aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore22
-rw-r--r--build.gradle121
-rw-r--r--gradle.properties1
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 52271 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rw-r--r--gradlew164
-rw-r--r--gradlew.bat90
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/AllowEmptyHTMLTag.java74
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/CustomRenderItem.java167
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java80
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/GuiItemUsages.java165
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/GuiTextures.java47
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java100
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java1119
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java1704
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java584
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/RequestFocusListener.java63
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/Utils.java431
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/DevInfoPane.java106
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/FlipperInfoPane.java111
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java316
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/InfoPane.java44
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/QOLInfoPane.java344
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/ScrollableInfoPane.java34
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/SettingsInfoPane.java259
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/infopanes/TextInfoPane.java61
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElement.java15
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementButton.java35
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementText.java42
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java453
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java429
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java20
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/options/Options.java203
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/HTMLParagraphView.java30
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/HtmlImageGenerator.java125
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java68
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/LargeHTMLEditorKit.java132
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/LerpingFloat.java68
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/LerpingInteger.java68
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/SynchronousHTMLEditorKit.java36
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/TexLoc.java50
-rw-r--r--src/main/resources/assets/notenoughupdates/ascending_overlay.pngbin0 -> 1497 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/close.pngbin0 -> 2350 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/descending_overlay.pngbin0 -> 1513 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/help.pngbin0 -> 3500 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/item_edit.pngbin0 -> 11387 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/item_mask.pngbin0 -> 856 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/item_pane_tab_arrow.pngbin0 -> 7532 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/logo.pngbin0 -> 21782 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/logo_bg.pngbin0 -> 21390 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/logo_fg.pngbin0 -> 5823 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/next.pngbin0 -> 3912 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/next.xcfbin0 -> 8805 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/off.pngbin0 -> 1302 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/on.pngbin0 -> 1311 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/order_alphabetical.pngbin0 -> 1862 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/order_alphabetical_active.pngbin0 -> 1819 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/order_rarity.pngbin0 -> 1808 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/order_rarity_active.pngbin0 -> 1953 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/prev.pngbin0 -> 4508 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/prev.xcfbin0 -> 8371 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/prev_unhovered.pngbin0 -> 4553 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/rightarrow.pngbin0 -> 1212 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/rightarrow_overlay.pngbin0 -> 1112 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/settings.pngbin0 -> 3962 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_accessory.pngbin0 -> 1885 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_accessory_active.pngbin0 -> 1871 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_all.pngbin0 -> 1817 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_all_active.pngbin0 -> 1850 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_armor.pngbin0 -> 1792 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_armor_active.pngbin0 -> 1921 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_mob.pngbin0 -> 1800 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_mob_active.pngbin0 -> 1815 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_pet.pngbin0 -> 1902 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_pet_active.pngbin0 -> 1932 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_weapon.pngbin0 -> 749 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/sort_weapon_active.pngbin0 -> 1869 bytes
-rw-r--r--src/main/resources/assets/notenoughupdates/wkhtmltox.zipbin0 -> 31902481 bytes
-rw-r--r--src/main/resources/mcmod.info16
-rw-r--r--src/main/resources/mixins.notenoughupdates.json8
80 files changed, 8011 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..2c770e09
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+# eclipse
+bin
+*.launch
+.settings
+.metadata
+.classpath
+.project
+
+# idea
+out
+*.ipr
+*.iws
+*.iml
+.idea
+
+# gradle
+build
+.gradle
+
+# other
+eclipse
+run
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..29128fba
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,121 @@
+buildscript {
+ repositories {
+ jcenter()
+ maven { url = "https://files.minecraftforge.net/maven" }
+ maven { url = "https://repo.spongepowered.org/maven" }
+ }
+ dependencies {
+ classpath 'net.minecraftforge.gradle:ForgeGradle:2.1-SNAPSHOT'
+ classpath 'org.spongepowered:mixingradle:0.6-SNAPSHOT'
+ classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.4'
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'net.minecraftforge.gradle.forge'
+apply plugin: 'org.spongepowered.mixin'
+apply plugin: 'com.github.johnrengelman.shadow'
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+version = "1.0"
+group= "io.github.moulberry"
+archivesBaseName = "NotEnoughUpdates"
+String modid = "notenoughupdates"
+String mixinClassifier = "dep"
+
+minecraft {
+ version = "1.8.9-11.15.1.2318-1.8.9"
+ runDir = "run"
+ mappings = "stable_20"
+}
+
+repositories {
+ jcenter()
+ maven { url 'https://repo.spongepowered.org/maven/' }
+}
+
+dependencies {
+ compile('org.spongepowered:mixin:0.7.11-SNAPSHOT')
+ compile('org.kohsuke:github-api:1.108')
+ compile('com.fasterxml.jackson.core:jackson-core:2.10.2')
+ compile('info.bliki.wiki:bliki-core:3.1.0')
+}
+
+mixin {
+ add sourceSets.main, "mixins.${modid}.refmap.json"
+}
+
+jar {
+ manifest.attributes(
+ 'TweakClass': 'org.spongepowered.asm.launch.MixinTweaker',
+ 'MixinConfigs': "mixins.${modid}.json",
+ 'FMLCorePluginContainsFMLMod': true,
+ "ForceLoadAsMod": true
+ )
+}
+
+shadowJar {
+ dependencies {
+ include(dependency('org.kohsuke:github-api:1.108'))
+ include(dependency('org.spongepowered:mixin:0.7.11-SNAPSHOT'))
+
+ include(dependency('commons-io:commons-io'))
+ include(dependency('org.apache.commons:commons-lang3'))
+ include(dependency('com.fasterxml.jackson.core:jackson-databind:2.10.2'))
+ include(dependency('com.fasterxml.jackson.core:jackson-annotations:2.10.2'))
+ include(dependency('com.fasterxml.jackson.core:jackson-core:2.10.2'))
+
+ include(dependency('info.bliki.wiki:bliki-core:3.1.0'))
+ include(dependency('org.slf4j:slf4j-api:1.7.18'))
+ include(dependency('org.luaj:luaj-jse:3.0.1'))
+ }
+
+ relocate 'com.fasterxml.jackson', 'neu.com.fasterxml.jackson'
+ relocate 'org.slf4j', 'neu.org.slf4j'
+
+ exclude 'module-info.class'
+ exclude 'dummyThing'
+ exclude 'LICENSE.txt'
+
+ classifier = mixinClassifier
+}
+
+reobf {
+ shadowJar {
+ mappingType = 'SEARGE'
+ }
+}
+
+
+task runClientFix {
+ doLast {
+ String fileName = "${archivesBaseName}-${version}-${mixinClassifier}.jar"
+ ant.move file: "${buildDir}/libs/${fileName}", tofile: "${projectDir}/run/mods/${fileName}"
+ ant.delete file: "${buildDir}/libs/${archivesBaseName}-${version}.jar"
+ }
+}
+
+runClient {
+ standardInput = System.in
+}
+
+build.dependsOn(shadowJar)
+runClient.dependsOn(build)
+runClient.dependsOn(runClientFix)
+
+processResources
+{
+ inputs.property "version", project.version
+ inputs.property "mcversion", project.minecraft.version
+
+ from(sourceSets.main.resources.srcDirs) {
+ include 'mcmod.info'
+ expand 'version':project.version, 'mcversion':project.minecraft.version
+ }
+
+ from(sourceSets.main.resources.srcDirs) {
+ exclude 'mcmod.info'
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..4687f10d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx2G
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..30d399d8
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..671daab2
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Sep 14 12:28:28 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 00000000..91a7e269
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/AllowEmptyHTMLTag.java b/src/main/java/io/github/moulberry/notenoughupdates/AllowEmptyHTMLTag.java
new file mode 100644
index 00000000..3cf5ef31
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/AllowEmptyHTMLTag.java
@@ -0,0 +1,74 @@
+package io.github.moulberry.notenoughupdates;
+
+import info.bliki.htmlcleaner.TagNode;
+import info.bliki.wiki.filter.ITextConverter;
+import info.bliki.wiki.model.IWikiModel;
+import info.bliki.wiki.tags.HTMLTag;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public class AllowEmptyHTMLTag extends HTMLTag {
+ public AllowEmptyHTMLTag(String name) {
+ super(name);
+ }
+
+ public void renderHTML(ITextConverter converter, Appendable buf, IWikiModel model) throws IOException {
+ boolean newLinesAfterTag = false;
+ boolean newLinesAfterChildren = false;
+ TagNode node = this;
+ String name = node.getName();
+ List<Object> children = node.getChildren();
+
+ if (NEW_LINES) {
+ switch (name) {
+ case "div":
+ case "p":
+ case "li":
+ case "td":
+ buf.append('\n');
+ break;
+ case "table":
+ case "ul":
+ case "ol":
+ case "th":
+ case "tr":
+ buf.append('\n');
+ newLinesAfterTag = true;
+ newLinesAfterChildren = true;
+ break;
+ case "pre":
+ buf.append('\n');
+ newLinesAfterTag = false;
+ newLinesAfterChildren = true;
+ break;
+ case "blockquote":
+ newLinesAfterChildren = true;
+ break;
+ }
+ }
+ buf.append('<');
+ buf.append(name);
+
+ Map<String, String> tagAtttributes = node.getAttributes();
+
+ appendAttributes(buf, tagAtttributes);
+
+ if (children.size() == 0) {
+ buf.append(" />");
+ } else {
+ buf.append('>');
+ if (newLinesAfterTag) {
+ buf.append('\n');
+ }
+ converter.nodesToText(children, buf, model);
+ if (newLinesAfterChildren) {
+ buf.append('\n');
+ }
+ buf.append("</");
+ buf.append(node.getName());
+ buf.append('>');
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/CustomRenderItem.java b/src/main/java/io/github/moulberry/notenoughupdates/CustomRenderItem.java
new file mode 100644
index 00000000..bf194e33
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/CustomRenderItem.java
@@ -0,0 +1,167 @@
+package io.github.moulberry.notenoughupdates;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.*;
+import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.client.renderer.texture.TextureUtil;
+import net.minecraft.client.renderer.tileentity.TileEntityItemStackRenderer;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.client.resources.model.IBakedModel;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.Vec3i;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class CustomRenderItem {
+
+ public static void renderItemIntoGUI(RenderItem renderItem, ItemStack stack, int x, int y, boolean effect) {
+ IBakedModel ibakedmodel = renderItem.getItemModelMesher().getItemModel(stack);
+ GlStateManager.pushMatrix();
+ Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.locationBlocksTexture);
+ Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture).setBlurMipmap(false, false);
+ GlStateManager.enableRescaleNormal();
+ GlStateManager.enableAlpha();
+ GlStateManager.alphaFunc(516, 0.1F);
+ GlStateManager.enableBlend();
+ GlStateManager.blendFunc(770, 771);
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ setupGuiTransform(x, y, ibakedmodel.isGui3d());
+ ibakedmodel = net.minecraftforge.client.ForgeHooksClient.handleCameraTransforms(ibakedmodel, ItemCameraTransforms.TransformType.GUI);
+ renderItem(renderItem, stack, ibakedmodel, effect);
+ GlStateManager.disableAlpha();
+ GlStateManager.disableRescaleNormal();
+ GlStateManager.disableLighting();
+ GlStateManager.popMatrix();
+ Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.locationBlocksTexture);
+ Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture).restoreLastBlurMipmap();
+ }
+
+ private static void setupGuiTransform(int xPosition, int yPosition, boolean isGui3d) {
+ GlStateManager.translate((float)xPosition, (float)yPosition, 0);
+ GlStateManager.translate(8.0F, 8.0F, 0.0F);
+ GlStateManager.scale(1.0F, 1.0F, -1.0F);
+ GlStateManager.scale(0.5F, 0.5F, 0.5F);
+
+ if (isGui3d) {
+ GlStateManager.scale(40.0F, 40.0F, 40.0F);
+ GlStateManager.rotate(210.0F, 1.0F, 0.0F, 0.0F);
+ GlStateManager.rotate(-135.0F, 0.0F, 1.0F, 0.0F);
+ GlStateManager.enableLighting();
+ } else {
+ GlStateManager.scale(64.0F, 64.0F, 64.0F);
+ GlStateManager.rotate(180.0F, 1.0F, 0.0F, 0.0F);
+ GlStateManager.disableLighting();
+ }
+ }
+
+ public static void renderItem(RenderItem renderItem, ItemStack stack, IBakedModel model, boolean effect) {
+ if (stack != null) {
+ GlStateManager.pushMatrix();
+ GlStateManager.scale(0.5F, 0.5F, 0.5F);
+
+ if (model.isBuiltInRenderer()) {
+ GlStateManager.rotate(180.0F, 0.0F, 1.0F, 0.0F);
+ GlStateManager.translate(-0.5F, -0.5F, -0.5F);
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ GlStateManager.enableRescaleNormal();
+ TileEntityItemStackRenderer.instance.renderByItem(stack);
+ } else {
+ GlStateManager.translate(-0.5F, -0.5F, -0.5F);
+ Class<?>[] paramsRM = new Class[]{IBakedModel.class, ItemStack.class};
+ Method renderModelMethod = Utils.getMethod(RenderItem.class, paramsRM,
+ "renderModel", "func_175036_a");
+ if(renderModelMethod != null) {
+ renderModelMethod.setAccessible(true);
+ try {
+ renderModelMethod.invoke(renderItem, model, stack);
+ } catch(Exception e) {}
+ }
+
+ if (stack.hasEffect() && effect) {
+ Class<?>[] paramsRE = new Class[]{IBakedModel.class};
+ Method renderEffectMethod = Utils.getMethod(RenderItem.class, paramsRE,
+ "renderEffect", "func_180451_a");
+ if(renderEffectMethod != null) {
+ renderEffectMethod.setAccessible(true);
+ try {
+ renderEffectMethod.invoke(renderItem, model);
+ } catch(Exception e) {}
+ }
+ }
+ }
+
+ GlStateManager.popMatrix();
+ }
+ }
+
+ /*private static void renderModel(IBakedModel model, ItemStack stack)
+ {
+ renderModel(model, -1, stack);
+ }
+
+ private static void renderModel(IBakedModel model, int color)
+ {
+ renderModel(model, color, (ItemStack)null);
+ }
+
+ private static void renderModel(IBakedModel model, int color, ItemStack stack)
+ {
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+ worldrenderer.begin(7, DefaultVertexFormats.ITEM);
+
+ for (EnumFacing enumfacing : EnumFacing.values())
+ {
+ renderQuads(worldrenderer, model.getFaceQuads(enumfacing), color, stack);
+ }
+
+ renderQuads(worldrenderer, model.getGeneralQuads(), color, stack);
+ tessellator.draw();
+ }
+
+ private static void putQuadNormal(WorldRenderer renderer, BakedQuad quad)
+ {
+ Vec3i vec3i = quad.getFace().getDirectionVec();
+ renderer.putNormal((float)vec3i.getX(), (float)vec3i.getY(), (float)vec3i.getZ());
+ }
+
+ private static void renderQuad(WorldRenderer renderer, BakedQuad quad, int color)
+ {
+ renderer.addVertexData(quad.getVertexData());
+ renderer.putColor4(color);
+ putQuadNormal(renderer, quad);
+ }
+
+ private static void renderQuads(WorldRenderer renderer, List<BakedQuad> quads, int color, ItemStack stack)
+ {
+ boolean flag = color == -1 && stack != null;
+ int i = 0;
+
+ for (int j = quads.size(); i < j; ++i)
+ {
+ BakedQuad bakedquad = (BakedQuad)quads.get(i);
+ int k = color;
+
+ if (flag && bakedquad.hasTintIndex())
+ {
+ k = stack.getItem().getColorFromItemStack(stack, bakedquad.getTintIndex());
+
+ if (EntityRenderer.anaglyphEnable)
+ {
+ k = TextureUtil.anaglyphColor(k);
+ }
+
+ k = k | -16777216;
+ }
+
+ net.minecraftforge.client.model.pipeline.LightUtil.renderQuadColor(renderer, bakedquad, k);
+ }
+ }*/
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java
new file mode 100644
index 00000000..6916372b
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java
@@ -0,0 +1,80 @@
+package io.github.moulberry.notenoughupdates;
+
+import com.google.gson.JsonObject;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.inventory.GuiCrafting;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.inventory.ContainerWorkbench;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+
+public class GuiItemRecipe extends GuiCrafting {
+
+ private ItemStack[] craftMatrix;
+ private String text;
+ private String craftText = "";
+ private NEUManager manager;
+
+ public GuiItemRecipe(ItemStack[] craftMatrix, JsonObject result, String text, NEUManager manager) {
+ super(Minecraft.getMinecraft().thePlayer.inventory, Minecraft.getMinecraft().theWorld);
+ this.craftMatrix = craftMatrix;
+ this.text = text;
+ this.manager = manager;
+
+ ContainerWorkbench cw = (ContainerWorkbench) this.inventorySlots;
+ for(int i=0; i<Math.min(craftMatrix.length, 9); i++) {
+ if(craftMatrix[i] == null) continue;
+ cw.craftMatrix.setInventorySlotContents(i, craftMatrix[i]);
+ }
+ if(result.has("crafttext")) {
+ craftText = result.get("crafttext").getAsString();
+ }
+ cw.craftResult.setInventorySlotContents(0, manager.jsonToStack(result));
+ }
+
+ protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
+ String t = text.equals("") ? I18n.format("container.crafting", new Object[0]) : text;
+
+ Utils.drawStringScaledMaxWidth(t, fontRendererObj, 28, 6, t.contains("\u00a7"), xSize-38, 4210752);
+ this.fontRendererObj.drawString(I18n.format("container.inventory", new Object[0]), 8, this.ySize - 96 + 2, 4210752);
+
+ Utils.drawStringCenteredScaledMaxWidth(craftText, fontRendererObj, 132, 25,
+ false, 75, 4210752);
+ }
+
+ protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {
+ }
+
+ protected void handleMouseClick(Slot slotIn, int slotId, int clickedButton, int clickType) {
+ ItemStack click = null;
+ if(slotId >= 1 && slotId <= 9) {
+ click = craftMatrix[slotId-1];
+ } else if(slotId == 0) {
+ ContainerWorkbench cw = (ContainerWorkbench) this.inventorySlots;
+ click = cw.craftResult.getStackInSlot(0);
+ }
+ if(click != null) {
+ if(clickedButton == 0) {
+ manager.displayGuiItemRecipe(manager.getInternalNameForItem(click), "");
+ } else if(clickedButton == 1) {
+ manager.displayGuiItemUsages(manager.getInternalNameForItem(click), "");
+ }
+ }
+ }
+
+ /*public void handleMouseInput() throws IOException {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int height = scaledresolution.getScaledHeight();
+
+ int mouseX = Mouse.getX() / scaledresolution.getScaleFactor();
+ int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor();
+ if(mouseY > this.guiTop + this.ySize - 94 || mouseY < this.guiTop ||
+ mouseX < this.guiLeft || mouseX > this.guiLeft+this.xSize) {
+ //Potentially allow mouse input in the future. For now this is still broken.
+ //super.handleMouseInput();
+ }
+ }*/
+
+ public void onCraftMatrixChanged(IInventory inventoryIn){}
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/GuiItemUsages.java b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemUsages.java
new file mode 100644
index 00000000..ceb4e5d4
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemUsages.java
@@ -0,0 +1,165 @@
+package io.github.moulberry.notenoughupdates;
+
+import com.google.gson.JsonObject;
+import io.github.moulberry.notenoughupdates.util.TexLoc;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.inventory.GuiCrafting;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.inventory.ContainerWorkbench;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.io.IOException;
+import java.util.List;
+
+public class GuiItemUsages extends GuiCrafting {
+ private static final ResourceLocation resourcePacksTexture = new ResourceLocation("textures/gui/resource_packs.png");
+
+ private List<ItemStack[]> craftMatrices;
+ private List<JsonObject> results;
+ private int currentIndex = 0;
+
+ private String text;
+ private String craftText = "";
+ private String collectionText = "";
+ private NEUManager manager;
+
+ private TexLoc left = new TexLoc(0, 0, Keyboard.KEY_N);
+ private TexLoc right = new TexLoc(0, 0, Keyboard.KEY_M);
+
+ public GuiItemUsages(List<ItemStack[]> craftMatrices, List<JsonObject> results, String text, NEUManager manager) {
+ super(Minecraft.getMinecraft().thePlayer.inventory, Minecraft.getMinecraft().theWorld);
+
+ this.craftMatrices = craftMatrices;
+ this.results = results;
+ this.text = text;
+ this.manager = manager;
+
+ setIndex(0);
+ }
+
+ private void setIndex(int index) {
+ if(index < 0 || index >= craftMatrices.size()) {
+ return;
+ } else {
+ currentIndex = index;
+
+ ContainerWorkbench cw = (ContainerWorkbench) this.inventorySlots;
+ for(int i=0; i<Math.min(craftMatrices.get(currentIndex).length, 9); i++) {
+ cw.craftMatrix.setInventorySlotContents(i, craftMatrices.get(currentIndex)[i]);
+ }
+ if(results.get(currentIndex).has("crafttext")) {
+ craftText = results.get(currentIndex).get("crafttext").getAsString();
+ }
+ cw.craftResult.setInventorySlotContents(0, manager.jsonToStack(results.get(currentIndex)));
+ }
+ }
+
+ protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
+ String t = "Crafting Usages";
+
+ int guiX = mouseX - guiLeft;
+ int guiY = mouseY - guiTop;
+
+ int buttonWidth = 7;
+ int buttonHeight = 11;
+
+ boolean leftSelected = false;
+ boolean rightSelected = false;
+
+ if(guiY > + 63 && guiY < + 63 + buttonHeight) {
+ if(guiX > + 110 && guiX < 110 + buttonWidth) {
+ leftSelected = true;
+ } else if(guiX > 147 && guiX < 147 + buttonWidth) {
+ rightSelected = true;
+ }
+ }
+
+ Minecraft.getMinecraft().getTextureManager().bindTexture(resourcePacksTexture);
+ //Left arrow
+ Utils.drawTexturedRect(110, 63, 7, 11, 34/256f, 48/256f,
+ 5/256f + (leftSelected ? 32/256f : 0), 27/256f + (leftSelected ? 32/256f : 0));
+ //Right arrow
+ Utils.drawTexturedRect(147, 63, 7, 11, 10/256f, 24/256f,
+ 5/256f + (rightSelected ? 32/256f : 0), 27/256f + (rightSelected ? 32/256f : 0));
+ GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
+
+ String str = (currentIndex+1)+"/"+craftMatrices.size();
+ Utils.drawStringCenteredScaledMaxWidth(str, fontRendererObj, 132, 69,
+ false, 24, Color.BLACK.getRGB());
+
+
+ Utils.drawStringCenteredScaledMaxWidth(craftText, fontRendererObj, 132, 25,
+ false, 75, 4210752);
+
+
+ Utils.drawStringScaledMaxWidth(t, fontRendererObj, 28, 6, t.contains("\u00a7"), xSize-38, 4210752);
+ this.fontRendererObj.drawString(I18n.format("container.inventory", new Object[0]), 8, this.ySize - 96 + 2, 4210752);
+ }
+
+ protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {
+ }
+
+ @Override
+ public void handleKeyboardInput() throws IOException {
+ super.handleKeyboardInput(); //TODO: r and u
+ left.handleKeyboardInput();
+ right.handleKeyboardInput();
+ }
+
+ @Override
+ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
+ super.mouseClicked(mouseX, mouseY, mouseButton);
+
+ int guiX = mouseX - guiLeft;
+ int guiY = mouseY - guiTop;
+
+ int buttonWidth = 7;
+ int buttonHeight = 11;
+
+ if(guiY > + 63 && guiY < + 63 + buttonHeight) {
+ if(guiX > + 110 && guiX < 110 + buttonWidth) {
+ setIndex(currentIndex-1);
+ } else if(guiX > 147 && guiX < 147 + buttonWidth) {
+ setIndex(currentIndex+1);
+ }
+ }
+ }
+
+ protected void handleMouseClick(Slot slotIn, int slotId, int clickedButton, int clickType) {
+ ItemStack click = null;
+ if(slotId >= 1 && slotId <= 9) {
+ click = craftMatrices.get(currentIndex)[slotId-1];
+ } else if(slotId == 0) {
+ ContainerWorkbench cw = (ContainerWorkbench) this.inventorySlots;
+ click = cw.craftResult.getStackInSlot(0);
+ }
+ if(click != null) {
+ if(clickedButton == 0) {
+ manager.displayGuiItemRecipe(manager.getInternalNameForItem(click), "");
+ } else if(clickedButton == 1) {
+ manager.displayGuiItemUsages(manager.getInternalNameForItem(click), "");
+ }
+ }
+ }
+
+ /*public void handleMouseInput() throws IOException {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int height = scaledresolution.getScaledHeight();
+
+ int mouseX = Mouse.getX() / scaledresolution.getScaleFactor();
+ int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor();
+ if(mouseY > this.guiTop + this.ySize - 94 || mouseY < this.guiTop ||
+ mouseX < this.guiLeft || mouseX > this.guiLeft+this.xSize) {
+ //Potentially allow mouse input in the future. For now this is still broken.
+ //super.handleMouseInput();
+ }
+ }*/
+
+ public void onCraftMatrixChanged(IInventory inventoryIn){}
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/GuiTextures.java b/src/main/java/io/github/moulberry/notenoughupdates/GuiTextures.java
new file mode 100644
index 00000000..48db19a5
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/GuiTextures.java
@@ -0,0 +1,47 @@
+package io.github.moulberry.notenoughupdates;
+
+import net.minecraft.util.ResourceLocation;
+
+public class GuiTextures {
+
+ private GuiTextures() {} //Not instantiable. Use import static to access class members.
+
+ public static final ResourceLocation itemPaneTabArrow = new ResourceLocation("notenoughupdates:item_pane_tab_arrow.png");
+ //public static final ResourceLocation prev = new ResourceLocation("notenoughupdates:prev.png");
+ //public static final ResourceLocation next = new ResourceLocation("notenoughupdates:next.png");
+ public static final ResourceLocation rightarrow_overlay = new ResourceLocation("notenoughupdates:rightarrow_overlay.png");
+ public static final ResourceLocation rightarrow = new ResourceLocation("notenoughupdates:rightarrow.png");
+ public static final ResourceLocation item_edit = new ResourceLocation("notenoughupdates:item_edit.png");
+ public static final ResourceLocation close = new ResourceLocation("notenoughupdates:close.png");
+ public static final ResourceLocation settings = new ResourceLocation("notenoughupdates:settings.png");
+ public static final ResourceLocation off = new ResourceLocation("notenoughupdates:off.png");
+ public static final ResourceLocation on = new ResourceLocation("notenoughupdates:on.png");
+ public static final ResourceLocation help = new ResourceLocation("notenoughupdates:help.png");
+
+ public static final ResourceLocation item_mask = new ResourceLocation("notenoughupdates:item_mask.png");
+
+ public static final ResourceLocation logo = new ResourceLocation("notenoughupdates:logo.png");
+ public static final ResourceLocation logo_fg = new ResourceLocation("notenoughupdates:logo_fg.png");
+ public static final ResourceLocation logo_bg = new ResourceLocation("notenoughupdates:logo_bg.png");
+
+ public static final ResourceLocation sort_all = new ResourceLocation("notenoughupdates:sort_all.png");
+ public static final ResourceLocation sort_mob = new ResourceLocation("notenoughupdates:sort_mob.png");
+ public static final ResourceLocation sort_pet = new ResourceLocation("notenoughupdates:sort_pet.png");
+ public static final ResourceLocation sort_tool = new ResourceLocation("notenoughupdates:sort_weapon.png");
+ public static final ResourceLocation sort_armor = new ResourceLocation("notenoughupdates:sort_armor.png");
+ public static final ResourceLocation sort_accessory = new ResourceLocation("notenoughupdates:sort_accessory.png");
+ public static final ResourceLocation sort_all_active = new ResourceLocation("notenoughupdates:sort_all_active.png");
+ public static final ResourceLocation sort_mob_active = new ResourceLocation("notenoughupdates:sort_mob_active.png");
+ public static final ResourceLocation sort_pet_active = new ResourceLocation("notenoughupdates:sort_pet_active.png");
+ public static final ResourceLocation sort_tool_active = new ResourceLocation("notenoughupdates:sort_weapon_active.png");
+ public static final ResourceLocation sort_armor_active = new ResourceLocation("notenoughupdates:sort_armor_active.png");
+ public static final ResourceLocation sort_accessory_active = new ResourceLocation("notenoughupdates:sort_accessory_active.png");
+
+ public static final ResourceLocation order_alphabetical = new ResourceLocation("notenoughupdates:order_alphabetical.png");
+ public static final ResourceLocation order_rarity = new ResourceLocation("notenoughupdates:order_rarity.png");
+ public static final ResourceLocation order_alphabetical_active = new ResourceLocation("notenoughupdates:order_alphabetical_active.png");
+ public static final ResourceLocation order_rarity_active = new ResourceLocation("notenoughupdates:order_rarity_active.png");
+ public static final ResourceLocation ascending_overlay = new ResourceLocation("notenoughupdates:ascending_overlay.png");
+ public static final ResourceLocation descending_overlay = new ResourceLocation("notenoughupdates:descending_overlay.png");
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java
new file mode 100644
index 00000000..a89bd1dc
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java
@@ -0,0 +1,100 @@
+package io.github.moulberry.notenoughupdates;
+
+import org.kohsuke.github.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class NEUIO {
+
+ private final String accessToken;
+
+ public NEUIO(String accessToken) {
+ this.accessToken = accessToken;
+ }
+
+ /**
+ * Creates a new branch, commits to it with a single file change and submits a pull request from the new branch
+ * back to the master branch.
+ */
+ public boolean createNewRequest(String newBranchName, String prTitle, String prBody, String filename, String content) {
+ try {
+ GitHub github = new GitHubBuilder().withOAuthToken(accessToken).build();
+ System.out.println("Getting repo");
+
+ //https://github.com/Moulberry/NotEnoughUpdates-REPO
+ GHRepository repo = github.getRepositoryById("247692460");
+
+ System.out.println("Getting last commit");
+ String lastCommitSha = repo.getRef("heads/master").getObject().getSha();
+ System.out.println("Last master commit sha: " + lastCommitSha);
+
+ String lastTreeSha = repo.getCommit(lastCommitSha).getTree().getSha();
+
+ GHTreeBuilder tb = repo.createTree();
+ tb.baseTree(lastTreeSha);
+ tb.add(filename, content, false);
+ GHTree tree = tb.create();
+ System.out.println("Created new tree: " + tree.getSha());
+
+ GHCommitBuilder cb = repo.createCommit();
+ cb.message(prTitle);
+ cb.tree(tree.getSha());
+ cb.parent(lastCommitSha);
+ GHCommit commit = cb.create();
+ System.out.println("Created commit: " + commit.getSHA1());
+
+ repo.createRef("refs/heads/"+newBranchName, commit.getSHA1());
+ System.out.println("Set new branch head to commit.");
+
+ repo.createPullRequest(prTitle, newBranchName, "master", prBody);
+ return true;
+ } catch(IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * @param oldShas Map from filename (eg. BOW.json) to the sha in the local repository
+ * @return Map from filename to the new shas
+ */
+ public Map<String, String> getChangedItems(Map<String, String> oldShas) {
+ HashMap<String, String> changedFiles = new HashMap<>();
+ try {
+ GitHub github = new GitHubBuilder().withOAuthToken(accessToken).build();
+ GHRepository repo = github.getRepositoryById("247692460");
+
+ for(GHContent content : repo.getDirectoryContent("items")) {
+ String oldSha = oldShas.get(content.getName());
+ if(!content.getSha().equals(oldSha)) {
+ changedFiles.put(content.getName(), content.getSha());
+ }
+ }
+ } catch(IOException e) {
+ return null;
+ }
+ return changedFiles;
+ }
+
+ /**
+ * Takes set of filename (eg. BOW.json) and returns map from that filename to the individual download link.
+ */
+ public Map<String, String> getItemsDownload(Set<String> filename) {
+ HashMap<String, String> downloadUrls = new HashMap<>();
+ try {
+ GitHub github = new GitHubBuilder().withOAuthToken(accessToken).build();
+ GHRepository repo = github.getRepositoryById("247692460");
+
+ for(GHContent content : repo.getDirectoryContent("items")) {
+ if(filename.contains(content.getName())) {
+ downloadUrls.put(content.getName(), content.getDownloadUrl());
+ }
+ }
+ } catch(IOException e) { }
+ return downloadUrls;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java
new file mode 100644
index 00000000..08939cca
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java
@@ -0,0 +1,1119 @@
+package io.github.moulberry.notenoughupdates;
+
+import com.google.common.io.CharSource;
+import com.google.gson.*;
+import io.github.moulberry.notenoughupdates.options.Options;
+import io.github.moulberry.notenoughupdates.util.HypixelApi;
+import javafx.scene.control.Alert;
+import net.minecraft.client.Minecraft;
+import net.minecraft.init.Items;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.*;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.ResourceLocation;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.ReaderInputStream;
+import org.apache.commons.lang3.tuple.Pair;
+import org.lwjgl.opengl.Display;
+
+import javax.swing.*;
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.text.NumberFormat;
+import java.util.*;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class NEUManager {
+
+ private final NotEnoughUpdates neu;
+ public final NEUIO neuio;
+ public final Gson gson;
+
+ private TreeMap<String, JsonObject> itemMap = new TreeMap<>();
+
+ private TreeMap<String, Set<String>> tagWordMap = new TreeMap<>();
+ private TreeMap<String, HashMap<String, List<Integer>>> titleWordMap = new TreeMap<>();
+ private TreeMap<String, HashMap<String, List<Integer>>> loreWordMap = new TreeMap<>();
+
+ public String viewItemAttemptID = null;
+ public long viewItemAttemptTime = 0;
+
+ public String currentProfile = "";
+ public final HypixelApi hypixelApi = new HypixelApi();
+
+ private ResourceLocation wkZip = new ResourceLocation("notenoughupdates:wkhtmltox.zip");
+ private Map<String, ItemStack> itemstackCache = new HashMap<>();
+
+ private static final String AUCTIONS_PRICE_URL = "https://moulberry.github.io/files/auc_avg_jsons/average_3day.json.gz";
+ private JsonObject auctionPricesJson = null;
+ private long auctionLastUpdate = 0;
+
+ private HashMap<String, CraftInfo> craftCost = new HashMap<>();
+
+ private HashMap<String, Set<String>> usagesMap = new HashMap<>();
+
+ public File configLocation;
+ private File itemsLocation;
+ private File itemShaLocation;
+ private JsonObject itemShaConfig;
+ private File configFile;
+ public Options config;
+
+ public NEUManager(NotEnoughUpdates neu, NEUIO neuio, File configLocation) {
+ this.neu = neu;
+ this.configLocation = configLocation;
+ this.neuio = neuio;
+
+ GsonBuilder gsonBuilder = new GsonBuilder().setPrettyPrinting();
+ gsonBuilder.registerTypeAdapter(Options.Option.class, Options.createSerializer());
+ gsonBuilder.registerTypeAdapter(Options.Option.class, Options.createDeserializer());
+ gson = gsonBuilder.create();
+
+ this.configFile = new File(configLocation, "config.json");
+ try {
+ configFile.createNewFile();
+ config = Options.loadFromFile(gson, configFile);
+ } catch(Exception e) {
+ config = new Options();
+ }
+
+ this.itemsLocation = new File(configLocation, "items");
+ itemsLocation.mkdir();
+
+ this.itemShaLocation = new File(configLocation, "itemSha.json");
+ try {
+ itemShaLocation.createNewFile();
+ itemShaConfig = getJsonFromFile(itemShaLocation);
+ if(itemShaConfig == null) itemShaConfig = new JsonObject();
+ } catch(IOException e) { }
+
+ File wkShell = new File(configLocation, "wkhtmltox/bin/wkhtmltoimage");
+ if(!wkShell.exists()) {
+ try {
+ InputStream is = Minecraft.getMinecraft().getResourceManager().getResource(wkZip).getInputStream();
+ unzip(is, configLocation);
+ } catch (IOException e) {
+ }
+ }
+
+ //Unused code, used to automatically grab items from auctions. Leaving here in case I need it.
+ /*try {
+ for(int j=0; j<=89; j++) {
+ JsonObject auctions0 = getJsonFromFile(new File(configLocation, "auctions/auctions"+j+".json"));
+
+ JsonArray arr = auctions0.getAsJsonArray("auctions");
+ for(int i=0; i<arr.size(); i++) {
+ JsonObject item0 = arr.get(i).getAsJsonObject();
+ try {
+ String item_bytes = item0.get("item_bytes").getAsString();
+
+ NBTTagCompound tag = CompressedStreamTools.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(item_bytes)));
+ tag = tag.getTagList("i", 10).getCompoundTagAt(0);
+ int id = tag.getShort("id");
+ int damage = tag.getShort("Damage");
+ tag = tag.getCompoundTag("tag");
+
+ String internalname = "";
+ if(tag != null && tag.hasKey("ExtraAttributes", 10)) {
+ NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes");
+
+ if(ea.hasKey("id", 8)) {
+ internalname = ea.getString("id");
+ }
+ }
+
+ String[] lore = new String[0];
+ NBTTagCompound display = tag.getCompoundTag("display");
+
+ if(display.hasKey("Lore", 9)) {
+ NBTTagList list = display.getTagList("Lore", 8);
+ lore = new String[list.tagCount()];
+ for(int k=0; k<list.tagCount(); k++) {
+ lore[k] = list.getStringTagAt(k);
+ }
+ }
+
+ if(Item.itemRegistry.getObject(new ResourceLocation(internalname.toLowerCase())) != null) {
+ continue;
+ }
+
+ String itemid = Item.getItemById(id).getRegistryName();
+ String displayname = display.getString("Name");
+ String[] info = new String[0];
+ String clickcommand = "";
+
+ File file = new File(itemsLocation, internalname+".json");
+ if(!file.exists()) {
+ writeItemJson(internalname, itemid, displayname, lore, info, clickcommand, damage, tag);
+ } else {
+ JsonObject existing = getJsonFromFile(file);
+ String nbttag = existing.get("nbttag").toString();
+ if(nbttag.length() > tag.toString().length()) {
+ writeItemJson(internalname, itemid, displayname, lore, info, clickcommand, damage, tag);
+ }
+ }
+
+ } catch(Exception e) {
+ }
+ }
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+
+ throw new RuntimeException();*/
+ }
+
+ public class CraftInfo {
+ public boolean fromRecipe = false;
+ public float craftCost = -1;
+ }
+
+ public CraftInfo getCraftCost(String internalname) {
+ if(craftCost.containsKey(internalname)) {
+ return craftCost.get(internalname);
+ } else {
+ CraftInfo ci = new CraftInfo();
+
+ JsonObject auctionInfo = getItemAuctionInfo(internalname);
+ JsonObject bazaarInfo = getBazaarInfo(internalname);
+
+ if(bazaarInfo != null) {
+ float bazaarInstantBuyPrice = bazaarInfo.get("curr_buy").getAsFloat();
+ ci.craftCost = bazaarInstantBuyPrice;
+ }
+ if(auctionInfo != null) {
+ float auctionPrice = auctionInfo.get("price").getAsFloat() / auctionInfo.get("count").getAsFloat();
+ if(ci.craftCost < 0 || auctionPrice < ci.craftCost) {
+ ci.craftCost = auctionPrice;
+ }
+ }
+ JsonObject item = getItemInformation().get(internalname);
+ if(item != null && item.has("recipe")) {
+ float craftPrice = 0;
+ JsonObject recipe = item.get("recipe").getAsJsonObject();
+
+ String[] x = {"1","2","3"};
+ String[] y = {"A","B","C"};
+ for(int i=0; i<9; i++) {
+ String name = y[i/3]+x[i%3];
+ String itemS = recipe.get(name).getAsString();
+ if(itemS.length() == 0) continue;
+
+ int count = 1;
+ if(itemS != null && itemS.split(":").length == 2) {
+ count = Integer.valueOf(itemS.split(":")[1]);
+ itemS = itemS.split(":")[0];
+ }
+ float compCost = getCraftCost(itemS).craftCost * count;
+ if(compCost < 0) {
+ craftCost.put(internalname, ci);
+ return ci;
+ } else {
+ craftPrice += compCost;
+ }
+ }
+
+ if(ci.craftCost < 0 || craftPrice < ci.craftCost) {
+ ci.craftCost = craftPrice;
+ ci.fromRecipe = true;
+ }
+ }
+ craftCost.put(internalname, ci);
+ return ci;
+ }
+ }
+
+ public void saveConfig() throws IOException {
+ config.saveToFile(gson, configFile);
+ }
+
+ public void updatePrices() {
+ if(System.currentTimeMillis() - auctionLastUpdate > 1000*60*30) { //30 minutes
+ craftCost.clear();
+ System.out.println("UPDATING PRICE INFORMATION");
+ auctionLastUpdate = System.currentTimeMillis();
+ try(Reader inReader = new InputStreamReader(new GZIPInputStream(new URL(AUCTIONS_PRICE_URL).openStream()))) {
+ auctionPricesJson = gson.fromJson(inReader, JsonObject.class);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public JsonObject getAuctionPricesJson() {
+ return auctionPricesJson;
+ }
+
+ public JsonObject getItemAuctionInfo(String internalname) {
+ JsonElement e = auctionPricesJson.get("prices").getAsJsonObject().get(internalname);
+ if(e == null) {
+ return null;
+ }
+ return e.getAsJsonObject();
+ }
+
+ public JsonObject getBazaarInfo(String internalname) {
+ JsonElement e = auctionPricesJson.get("bazaar").getAsJsonObject().get(internalname);
+ if(e == null) {
+ return null;
+ }
+ return e.getAsJsonObject();
+ }
+
+ public float getCostOfEnchants(String internalname, NBTTagCompound tag) {
+ float costOfEnchants = 0;
+ JsonObject info = getItemAuctionInfo(internalname);
+ if(info == null || !info.has("price")) {
+ return 0;
+ }
+ float price = getItemAuctionInfo(internalname).get("price").getAsFloat();
+ if(tag.hasKey("ExtraAttributes")) {
+ NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes");
+ if(ea.hasKey("enchantments")) {
+ JsonObject ench_prices = auctionPricesJson.get("ench_prices").getAsJsonObject();
+
+ NBTTagCompound enchs = ea.getCompoundTag("enchantments");
+ for(String ench : enchs.getKeySet()) {
+ int level = enchs.getInteger(ench);
+
+ for(Map.Entry<String, JsonElement> entry : ench_prices.entrySet()) {
+ if(matchEnch(ench, level, entry.getKey())) {
+ costOfEnchants += entry.getValue().getAsJsonObject().get("A").getAsFloat()*price +
+ entry.getValue().getAsJsonObject().get("B").getAsFloat();
+ break;
+ }
+ }
+ }
+ }
+ }
+ return costOfEnchants;
+ }
+
+ private boolean matchEnch(String ench, int level, String id) {
+ String idEnch = id.split(":")[0];
+ String idLevel = id.split(":")[1];
+
+ if(!ench.equals(idEnch)) {
+ return false;
+ }
+
+ if(String.valueOf(level).equals(idLevel)) {
+ return true;
+ }
+
+ if(idLevel.startsWith("LE")) {
+ int idLevelI = Integer.valueOf(idLevel.substring(2));
+ return level <= idLevelI;
+ } else if(idLevel.startsWith("GE")) {
+ int idLevelI = Integer.valueOf(idLevel.substring(2));
+ return level >= idLevelI;
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses a file in to a JsonObject.
+ */
+ public JsonObject getJsonFromFile(File file) throws IOException {
+ InputStream in = new FileInputStream(file);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+ JsonObject json = gson.fromJson(reader, JsonObject.class);
+ return json;
+ }
+
+ /**
+ * Called when the game is first loaded. Compares the local repository to the github repository and handles
+ * the downloading of new/updated files. This then calls the "loadItem" method for every item in the local
+ * repository.
+ */
+ public void loadItemInformation() {
+ if(config.autoupdate.value) {
+ JOptionPane pane = new JOptionPane("Getting items to download from remote repository.");
+ JDialog dialog = pane.createDialog("NotEnoughUpdates Remote Sync");
+ dialog.setModal(false);
+ //dialog.setVisible(true);
+
+ if (Display.isActive()) dialog.toFront();
+
+ HashMap<String, String> oldShas = new HashMap<>();
+ for (Map.Entry<String, JsonElement> entry : itemShaConfig.entrySet()) {
+ if (new File(itemsLocation, entry.getKey() + ".json").exists()) {
+ oldShas.put(entry.getKey() + ".json", entry.getValue().getAsString());
+ }
+ }
+ Map<String, String> changedFiles = neuio.getChangedItems(oldShas);
+
+ if (changedFiles != null) {
+ for (Map.Entry<String, String> changedFile : changedFiles.entrySet()) {
+ itemShaConfig.addProperty(changedFile.getKey().substring(0, changedFile.getKey().length() - 5),
+ changedFile.getValue());
+ }
+ try {
+ writeJson(itemShaConfig, itemShaLocation);
+ } catch (IOException e) {
+ }
+ }
+
+ if (Display.isActive()) dialog.toFront();
+
+ if (changedFiles != null && changedFiles.size() <= 20) {
+ Map<String, String> downloads = neuio.getItemsDownload(changedFiles.keySet());
+
+ String startMessage = "NotEnoughUpdates: Syncing with remote repository (";
+ int downloaded = 0;
+
+ for (Map.Entry<String, String> entry : downloads.entrySet()) {
+ pane.setMessage(startMessage + (++downloaded) + "/" + downloads.size() + ")\nCurrent: " + entry.getKey());
+ dialog.pack();
+ dialog.setVisible(true);
+ if (Display.isActive()) dialog.toFront();
+
+ File item = new File(itemsLocation, entry.getKey());
+ try {
+ item.createNewFile();
+ } catch (IOException e) {
+ }
+ try (BufferedInputStream inStream = new BufferedInputStream(new URL(entry.getValue()).openStream());
+ FileOutputStream fileOutputStream = new FileOutputStream(item)) {
+ byte dataBuffer[] = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = inStream.read(dataBuffer, 0, 1024)) != -1) {
+ fileOutputStream.write(dataBuffer, 0, bytesRead);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ } else {
+ //TODO: Store hard-coded value somewhere else
+ String dlUrl = "https://github.com/Moulberry/NotEnoughUpdates-REPO/archive/master.zip";
+
+ pane.setMessage("Downloading NEU Master Archive. (DL# >20)");
+ dialog.pack();
+ dialog.setVisible(true);
+ if (Display.isActive()) dialog.toFront();
+
+ File itemsZip = new File(configLocation, "neu-items-master.zip");
+ try {
+ itemsZip.createNewFile();
+ } catch (IOException e) {
+ }
+ try (BufferedInputStream inStream = new BufferedInputStream(new URL(dlUrl).openStream());
+ FileOutputStream fileOutputStream = new FileOutputStream(itemsZip)) {
+ byte dataBuffer[] = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = inStream.read(dataBuffer, 0, 1024)) != -1) {
+ fileOutputStream.write(dataBuffer, 0, bytesRead);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ pane.setMessage("Unzipping NEU Master Archive.");
+ dialog.pack();
+ dialog.setVisible(true);
+ if (Display.isActive()) dialog.toFront();
+
+ unzipIgnoreFirstFolder(itemsZip.getAbsolutePath(), configLocation.getAbsolutePath());
+ }
+
+ dialog.dispose();
+ }
+
+ for(File f : itemsLocation.listFiles()) {
+ loadItem(f.getName().substring(0, f.getName().length()-5));
+ }
+ }
+
+ /**
+ * Loads the item in to the itemMap and also stores various words associated with this item
+ * in to titleWordMap and loreWordMap. These maps are used in the searching algorithm.
+ * @param internalName
+ */
+ public void loadItem(String internalName) {
+ itemstackCache.remove(internalName);
+ try {
+ JsonObject json = getJsonFromFile(new File(itemsLocation, internalName + ".json"));
+ if(json == null) {
+ return;
+ }
+ itemMap.put(internalName, json);
+
+ if(json.has("recipe")) {
+ JsonObject recipe = json.get("recipe").getAsJsonObject();
+
+ String[] x = {"1","2","3"};
+ String[] y = {"A","B","C"};
+ for(int i=0; i<9; i++) {
+ String name = y[i/3]+x[i%3];
+ String itemS = recipe.get(name).getAsString();
+ if(itemS != null && itemS.split(":").length == 2) {
+ itemS = itemS.split(":")[0];
+ }
+
+ if(!usagesMap.containsKey(itemS)) {
+ usagesMap.put(itemS, new HashSet<>());
+ }
+ usagesMap.get(itemS).add(internalName);
+ }
+ }
+
+ if(json.has("displayname")) {
+ int wordIndex=0;
+ for(String str : json.get("displayname").getAsString().split(" ")) {
+ str = clean(str);
+ if(!titleWordMap.containsKey(str)) {
+ titleWordMap.put(str, new HashMap<>());
+ }
+ if(!titleWordMap.get(str).containsKey(internalName)) {
+ titleWordMap.get(str).put(internalName, new ArrayList<>());
+ }
+ titleWordMap.get(str).get(internalName).add(wordIndex);
+ wordIndex++;
+ }
+ }
+
+ if(json.has("lore")) {
+ int wordIndex=0;
+ for(JsonElement element : json.get("lore").getAsJsonArray()) {
+ for(String str : element.getAsString().split(" ")) {
+ str = clean(str);
+ if(!loreWordMap.containsKey(str)) {
+ loreWordMap.put(str, new HashMap<>());
+ }
+ if(!loreWordMap.get(str).containsKey(internalName)) {
+ loreWordMap.get(str).put(internalName, new ArrayList<>());
+ }
+ loreWordMap.get(str).get(internalName).add(wordIndex);
+ wordIndex++;
+ }
+ }
+ }
+ } catch(IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Searches a string for a query. This method is used to mimic the behaviour of the
+ * more complex map-based search function. This method is used for the chest-item-search feature.
+ */
+ public boolean searchString(String toSearch, String query) {
+ int lastMatch = -1;
+
+ toSearch = clean(toSearch).toLowerCase();
+ query = clean(query).toLowerCase();
+ String[] splitToSeach = toSearch.split(" ");
+ out:
+ for(String s : query.split(" ")) {
+ for(int i=0; i<splitToSeach.length; i++) {
+ if(lastMatch == -1 || lastMatch == i-1) {
+ if (splitToSeach[i].startsWith(s)) {
+ lastMatch = i;
+ continue out;
+ }
+ }
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks whether an itemstack matches a certain query, following the same rules implemented by the
+ * more complex map-based search function.
+ */
+ public boolean doesStackMatchSearch(ItemStack stack, String query) {
+ if(query.startsWith("title:")) {
+ query = query.substring(6);
+ return searchString(stack.getDisplayName(), query);
+ } else if(query.startsWith("desc:")) {
+ query = query.substring(5);
+ String lore = "";
+ NBTTagCompound tag = stack.getTagCompound();
+ if(tag != null) {
+ NBTTagCompound display = tag.getCompoundTag("display");
+ if (display.hasKey("Lore", 9)) {
+ NBTTagList list = display.getTagList("Lore", 8);
+ for (int i = 0; i < list.tagCount(); i++) {
+ lore += list.getStringTagAt(i) + " ";
+ }
+ }
+ }
+ return searchString(lore, query);
+ } else if(query.startsWith("id:")) {
+ query = query.substring(3);
+ String internalName = getInternalNameForItem(stack);
+ return query.equalsIgnoreCase(internalName);
+ } else {
+ boolean result = false;
+ if(!query.trim().contains(" ")) {
+ StringBuilder sb = new StringBuilder();
+ for(char c : query.toCharArray()) {
+ sb.append(c).append(" ");
+ }
+ result = result || searchString(stack.getDisplayName(), sb.toString());
+ }
+ result = result || searchString(stack.getDisplayName(), query);
+
+ String lore = "";
+ NBTTagCompound tag = stack.getTagCompound();
+ if(tag != null) {
+ NBTTagCompound display = tag.getCompoundTag("display");
+ if (display.hasKey("Lore", 9)) {
+ NBTTagList list = display.getTagList("Lore", 8);
+ for (int i = 0; i < list.tagCount(); i++) {
+ lore += list.getStringTagAt(i) + " ";
+ }
+ }
+ }
+
+ result = result || searchString(lore, query);
+
+ return result;
+ }
+ }
+
+ /**
+ * Returns the name of items which match a certain search query.
+ */
+ public Set<String> search(String query) {
+ LinkedHashSet<String> results = new LinkedHashSet<>();
+ if(query.startsWith("title:")) {
+ query = query.substring(6);
+ results.addAll(new TreeSet<>(search(query, titleWordMap)));
+ } else if(query.startsWith("desc:")) {
+ query = query.substring(5);
+ results.addAll(new TreeSet<>(search(query, loreWordMap)));
+ } else if(query.startsWith("id:")) {
+ query = query.substring(3);
+ results.addAll(new TreeSet<>(subMapWithKeysThatAreSuffixes(query.toUpperCase(), itemMap).keySet()));
+ } else {
+ if(!query.trim().contains(" ")) {
+ StringBuilder sb = new StringBuilder();
+ for(char c : query.toCharArray()) {
+ sb.append(c).append(" ");
+ }
+ results.addAll(new TreeSet<>(search(sb.toString(), titleWordMap)));
+ }
+ results.addAll(new TreeSet<>(search(query, titleWordMap)));
+ results.addAll(new TreeSet<>(search(query, loreWordMap)));
+ }
+ return results;
+ }
+
+ /**
+ * Splits a search query into an array of strings delimited by a space character. Then, matches the query to
+ * the start of words in the various maps (title & lore). The small query does not need to match the whole entry
+ * of the map, only the beginning. eg. "ench" and "encha" will both match "enchanted". All sub queries must
+ * follow a word matching the previous sub query. eg. "ench po" will match "enchanted pork" but will not match
+ * "pork enchanted".
+ */
+ private Set<String> search(String query, TreeMap<String, HashMap<String, List<Integer>>> wordMap) {
+ HashMap<String, List<Integer>> matches = null;
+
+ query = clean(query).toLowerCase();
+ for(String queryWord : query.split(" ")) {
+ HashMap<String, List<Integer>> matchesToKeep = new HashMap<>();
+ for(HashMap<String, List<Integer>> wordMatches : subMapWithKeysThatAreSuffixes(queryWord, wordMap).values()) {
+ if(wordMatches != null && !wordMatches.isEmpty()) {
+ if(matches == null) {
+ //Copy all wordMatches to titleMatches
+ for(String internalname : wordMatches.keySet()) {
+ if(!matchesToKeep.containsKey(internalname)) {
+ matchesToKeep.put(internalname, new ArrayList<>());
+ }
+ matchesToKeep.get(internalname).addAll(wordMatches.get(internalname));
+ }
+ } else {
+ for(String internalname : matches.keySet()) {
+ if(wordMatches.containsKey(internalname)) {
+ for(Integer newIndex : wordMatches.get(internalname)) {
+ if(matches.get(internalname).contains(newIndex-1)) {
+ if(!matchesToKeep.containsKey(internalname)) {
+ matchesToKeep.put(internalname, new ArrayList<>());
+ }
+ matchesToKeep.get(internalname).add(newIndex);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(matchesToKeep.isEmpty()) return new HashSet<>();
+ matches = matchesToKeep;
+ }
+
+ return matches.keySet();
+ }
+
+ /**
+ * From https://stackoverflow.com/questions/10711494/get-values-in-treemap-whose-string-keys-start-with-a-pattern
+ */
+ public <T> Map<String, T> subMapWithKeysThatAreSuffixes(String prefix, NavigableMap<String, T> map) {
+ if ("".equals(prefix)) return map;
+ String lastKey = createLexicographicallyNextStringOfTheSameLenght(prefix);
+ return map.subMap(prefix, true, lastKey, false);
+ }
+
+ String createLexicographicallyNextStringOfTheSameLenght(String input) {
+ final int lastCharPosition = input.length()-1;
+ String inputWithoutLastChar = input.substring(0, lastCharPosition);
+ char lastChar = input.charAt(lastCharPosition) ;
+ char incrementedLastChar = (char) (lastChar + 1);
+ return inputWithoutLastChar+incrementedLastChar;
+ }
+
+ private String clean(String str) {
+ return str.replaceAll("(\u00a7.)|[^0-9a-zA-Z ]", "").toLowerCase().trim();
+ }
+
+ public void showRecipe(JsonObject item) {
+ if(item.has("useneucraft") && item.get("useneucraft").getAsBoolean()) {
+ displayGuiItemRecipe(item.get("internalname").getAsString(), "");
+ } else if(item.has("clickcommand")) {
+ String clickcommand = item.get("clickcommand").getAsString();
+
+ if(clickcommand.equals("viewrecipe")) {
+ neu.sendChatMessage(
+ "/" + clickcommand + " " +
+ item.get("internalname").getAsString().split(";")[0]);
+ viewItemAttemptID = item.get("internalname").getAsString();
+ viewItemAttemptTime = System.currentTimeMillis();
+ } else if(clickcommand.equals("viewpotion")) {
+ neu.sendChatMessage(
+ "/" + clickcommand + " " +
+ item.get("internalname").getAsString().split(";")[0].toLowerCase());
+ viewItemAttemptID = item.get("internalname").getAsString();
+ viewItemAttemptTime = System.currentTimeMillis();
+ }
+ }
+ }
+
+ /**
+ * Takes an item stack and produces a JsonObject. This is used in the item editor.
+ */
+ public JsonObject getJsonForItem(ItemStack stack) {
+ NBTTagCompound tag = stack.getTagCompound() == null ? new NBTTagCompound() : stack.getTagCompound();
+
+ //Item lore
+ String[] lore = new String[0];
+ if(tag.hasKey("display", 10)) {
+ NBTTagCompound display = tag.getCompoundTag("display");
+
+ if(display.hasKey("Lore", 9)) {
+ NBTTagList list = display.getTagList("Lore", 8);
+ lore = new String[list.tagCount()];
+ for(int i=0; i<list.tagCount(); i++) {
+ lore[i] = list.getStringTagAt(i);
+ }
+ }
+ }
+
+ if(stack.getDisplayName().endsWith(" Recipes")) {
+ stack.setStackDisplayName(stack.getDisplayName().substring(0, stack.getDisplayName().length()-8));
+ }
+
+ if(lore.length > 0 && (lore[lore.length-1].contains("Click to view recipes!") ||
+ lore[lore.length-1].contains("Click to view recipe!"))) {
+ String[] lore2 = new String[lore.length-2];
+ System.arraycopy(lore, 0, lore2, 0, lore.length-2);
+ lore = lore2;
+ }
+
+ JsonObject json = new JsonObject();
+ json.addProperty("itemid", stack.getItem().getRegistryName());
+ json.addProperty("displayname", stack.getDisplayName());
+ json.addProperty("nbttag", tag.toString());
+ json.addProperty("damage", stack.getItemDamage());
+
+ JsonArray jsonlore = new JsonArray();
+ for(String line : lore) {
+ jsonlore.add(new JsonPrimitive(line));
+ }
+ json.add("lore", jsonlore);
+
+ return json;
+ }
+
+ public String getInternalNameForItem(ItemStack stack) {
+ NBTTagCompound tag = stack.getTagCompound();
+ //Internal id
+ if(tag != null && tag.hasKey("ExtraAttributes", 10)) {
+ NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes");
+
+ if(ea.hasKey("id", 8)) {
+ return ea.getString("id").replaceAll(":", "-");
+ }
+ }
+ return null;
+ }
+
+ //Currently unused in production.
+ public void writeItemToFile(ItemStack stack) {
+ String internalname = getInternalNameForItem(stack);
+
+ if(internalname == null) {
+ return;
+ }
+
+ JsonObject json = getJsonForItem(stack);
+ json.addProperty("internalname", internalname);
+ json.addProperty("clickcommand", "");
+ json.addProperty("modver", NotEnoughUpdates.VERSION);
+
+ try {
+ writeJson(json, new File(itemsLocation, internalname+".json"));
+ } catch (IOException e) {}
+
+ loadItem(internalname); //Collection: Cocoa Beans VII
+ }
+
+ public JsonObject createItemJson(String internalname, String itemid, String displayname, String[] lore,
+ String crafttext, String infoType, String[] info,
+ String clickcommand, int damage, NBTTagCompound nbttag) {
+ return createItemJson(new JsonObject(), internalname, itemid, displayname, lore, crafttext, infoType, info, clickcommand, damage, nbttag);
+ }
+
+ public JsonObject createItemJson(JsonObject base, String internalname, String itemid, String displayname, String[] lore,
+ String crafttext, String infoType, String[] info,
+ String clickcommand, int damage, NBTTagCompound nbttag) {
+ if(internalname == null || internalname.isEmpty()) {
+ return null;
+ }
+
+ JsonObject json = gson.fromJson(gson.toJson(base, JsonObject.class), JsonObject.class);
+ json.addProperty("internalname", internalname);
+ json.addProperty("itemid", itemid);
+ json.addProperty("displayname", displayname);
+ json.addProperty("crafttext", crafttext);
+ json.addProperty("clickcommand", clickcommand);
+ json.addProperty("damage", damage);
+ json.addProperty("nbttag", nbttag.toString());
+ json.addProperty("modver", NotEnoughUpdates.VERSION);
+ json.addProperty("infoType", infoType.toString());
+
+ if(info != null && info.length > 0) {
+ JsonArray jsoninfo = new JsonArray();
+ for (String line : info) {
+ jsoninfo.add(new JsonPrimitive(line));
+ }
+ json.add("info", jsoninfo);
+ }
+
+ JsonArray jsonlore = new JsonArray();
+ for(String line : lore) {
+ jsonlore.add(new JsonPrimitive(line));
+ }
+ json.add("lore", jsonlore);
+
+ return json;
+ }
+
+ public boolean writeItemJson(String internalname, String itemid, String displayname, String[] lore, String crafttext,
+ String infoType, String[] info, String clickcommand, int damage, NBTTagCompound nbttag) {
+ return writeItemJson(new JsonObject(), internalname, itemid, displayname, lore, crafttext, infoType, info, clickcommand, damage, nbttag);
+ }
+
+ public boolean writeItemJson(JsonObject base, String internalname, String itemid, String displayname, String[] lore,
+ String crafttext, String infoType, String[] info, String clickcommand, int damage, NBTTagCompound nbttag) {
+ JsonObject json = createItemJson(base, internalname, itemid, displayname, lore, crafttext, infoType, info, clickcommand, damage, nbttag);
+ if(json == null) {
+ return false;
+ }
+
+ try {
+ writeJsonDefaultDir(json, internalname+".json");
+ } catch(IOException e) {
+ return false;
+ }
+
+ loadItem(internalname);
+ return true;
+ }
+
+ public boolean uploadItemJson(String internalname, String itemid, String displayname, String[] lore, String crafttext, String infoType, String[] info,
+ String clickcommand, int damage, NBTTagCompound nbttag) {
+ JsonObject json = createItemJson(internalname, itemid, displayname, lore, crafttext, infoType, info, clickcommand, damage, nbttag);
+ if(json == null) {
+ return false;
+ }
+
+ String username = Minecraft.getMinecraft().thePlayer.getName();
+ String newBranchName = UUID.randomUUID().toString().substring(0, 8) + "-" + internalname + "-" + username;
+ String prTitle = internalname + "-" + username;
+ String prBody = "Internal name: " + internalname + "\nSubmitted by: " + username;
+ String file = "items/"+internalname+".json";
+ if(!neuio.createNewRequest(newBranchName, prTitle, prBody, file, gson.toJson(json))) {
+ return false;
+ }
+
+ try {
+ writeJsonDefaultDir(json, internalname+".json");
+ } catch(IOException e) {
+ return false;
+ }
+
+ loadItem(internalname);
+ return true;
+ }
+
+ public void writeJson(JsonObject json, File file) throws IOException {
+ file.createNewFile();
+
+ try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
+ writer.write(gson.toJson(json));
+ }
+ }
+
+ public void writeJsonDefaultDir(JsonObject json, String filename) throws IOException {
+ File file = new File(itemsLocation, filename);
+ writeJson(json, file);
+ }
+
+ public TreeMap<String, JsonObject> getItemInformation() {
+ return itemMap;
+ }
+
+ /**
+ * Stolen from https://www.journaldev.com/960/java-unzip-file-example
+ */
+ private static void unzipIgnoreFirstFolder(String zipFilePath, String destDir) {
+ File dir = new File(destDir);
+ // create output directory if it doesn't exist
+ if(!dir.exists()) dir.mkdirs();
+ FileInputStream fis;
+ //buffer for read and write data to file
+ byte[] buffer = new byte[1024];
+ try {
+ fis = new FileInputStream(zipFilePath);
+ ZipInputStream zis = new ZipInputStream(fis);
+ ZipEntry ze = zis.getNextEntry();
+ while(ze != null){
+ if(!ze.isDirectory()) {
+ String fileName = ze.getName();
+ fileName = fileName.substring(fileName.split("/")[0].length()+1);
+ File newFile = new File(destDir + File.separator + fileName);
+ //create directories for sub directories in zip
+ new File(newFile.getParent()).mkdirs();
+ FileOutputStream fos = new FileOutputStream(newFile);
+ int len;
+ while ((len = zis.read(buffer)) > 0) {
+ fos.write(buffer, 0, len);
+ }
+ fos.close();
+ }
+ //close this ZipEntry
+ zis.closeEntry();
+ ze = zis.getNextEntry();
+ }
+ //close last ZipEntry
+ zis.closeEntry();
+ zis.close();
+ fis.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Stolen from https://www.journaldev.com/960/java-unzip-file-example
+ */
+ private static void unzip(InputStream src, File dest) {
+ //buffer for read and write data to file
+ byte[] buffer = new byte[1024];
+ try {
+ ZipInputStream zis = new ZipInputStream(src);
+ ZipEntry ze = zis.getNextEntry();
+ while(ze != null){
+ if(!ze.isDirectory()) {
+ String fileName = ze.getName();
+ File newFile = new File(dest, fileName);
+ //create directories for sub directories in zip
+ new File(newFile.getParent()).mkdirs();
+ FileOutputStream fos = new FileOutputStream(newFile);
+ int len;
+ while ((len = zis.read(buffer)) > 0) {
+ fos.write(buffer, 0, len);
+ }
+ fos.close();
+ }
+ //close this ZipEntry
+ zis.closeEntry();
+ ze = zis.getNextEntry();
+ }
+ //close last ZipEntry
+ zis.closeEntry();
+ zis.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public ItemStack jsonToStack(JsonObject json) {
+ if(itemstackCache.containsKey(json.get("internalname").getAsString())) {
+ return itemstackCache.get(json.get("internalname").getAsString()).copy();
+ }
+ ItemStack stack = new ItemStack(Item.itemRegistry.getObject(
+ new ResourceLocation(json.get("itemid").getAsString())));
+
+ if(stack.getItem() == null) {
+ stack = new ItemStack(Items.diamond, 1, 10); //Purple broken texture item
+ } else {
+ if(json.has("damage")) {
+ stack.setItemDamage(json.get("damage").getAsInt());
+ }
+
+ if(json.has("nbttag")) {
+ try {
+ NBTTagCompound tag = JsonToNBT.getTagFromJson(json.get("nbttag").getAsString());
+ stack.setTagCompound(tag);
+ } catch(NBTException e) {
+ }
+ }
+
+ if(json.has("lore")) {
+ NBTTagCompound display = stack.getTagCompound().getCompoundTag("display");
+ NBTTagList lore = new NBTTagList();
+ for(JsonElement line : json.get("lore").getAsJsonArray()) {
+ String lineStr = line.getAsString();
+ if(!lineStr.contains("Click to view recipes!") &&
+ !lineStr.contains("Click to view recipe!")) {
+ lore.appendTag(new NBTTagString(lineStr));
+ }
+ }
+ display.setTag("Lore", lore);
+ NBTTagCompound tag = stack.getTagCompound();
+ tag.setTag("display", display);
+ stack.setTagCompound(tag);
+ }
+ }
+
+ itemstackCache.put(json.get("internalname").getAsString(), stack);
+ return stack;
+ }
+
+ public boolean displayGuiItemUsages(String internalName, String text) {
+ List<ItemStack[]> craftMatrices = new ArrayList<>();
+ List<JsonObject> results = new ArrayList<>();
+
+ if(!usagesMap.containsKey(internalName)) {
+ return false;
+ }
+
+ for(String internalNameResult : usagesMap.get(internalName)) {
+ JsonObject item = getItemInformation().get(internalNameResult);
+ results.add(item);
+
+ if(item != null && item.has("recipe")) {
+ JsonObject recipe = item.get("recipe").getAsJsonObject();
+
+ ItemStack[] craftMatrix = new ItemStack[9];
+
+ String[] x = {"1","2","3"};
+ String[] y = {"A","B","C"};
+ for(int i=0; i<9; i++) {
+ String name = y[i/3]+x[i%3];
+ String itemS = recipe.get(name).getAsString();
+ int count = 1;
+ if(itemS != null && itemS.split(":").length == 2) {
+ count = Integer.valueOf(itemS.split(":")[1]);
+ itemS = itemS.split(":")[0];
+ }
+ JsonObject craft = getItemInformation().get(itemS);
+ if(craft != null) {
+ ItemStack stack = jsonToStack(craft);
+ stack.stackSize = count;
+ craftMatrix[i] = stack;
+ }
+ }
+
+ craftMatrices.add(craftMatrix);
+ }
+ }
+
+ if(craftMatrices.size() > 0) {
+ Minecraft.getMinecraft().displayGuiScreen(new GuiItemUsages(craftMatrices, results, text, this));
+ return true;
+ }
+ return false;
+ }
+
+ public boolean displayGuiItemRecipe(String internalName, String text) {
+ JsonObject item = getItemInformation().get(internalName);
+ if(item != null && item.has("recipe")) {
+ JsonObject recipe = item.get("recipe").getAsJsonObject();
+
+ ItemStack[] craftMatrix = new ItemStack[9];
+
+ String[] x = {"1","2","3"};
+ String[] y = {"A","B","C"};
+ for(int i=0; i<9; i++) {
+ String name = y[i/3]+x[i%3];
+ String itemS = recipe.get(name).getAsString();
+ int count = 1;
+ if(itemS != null && itemS.split(":").length == 2) {
+ count = Integer.valueOf(itemS.split(":")[1]);
+ itemS = itemS.split(":")[0];
+ }
+ JsonObject craft = getItemInformation().get(itemS);
+ if(craft != null) {
+ ItemStack stack = jsonToStack(craft);
+ stack.stackSize = count;
+ craftMatrix[i] = stack;
+ }
+ }
+
+ Minecraft.getMinecraft().displayGuiScreen(new GuiItemRecipe(craftMatrix, item, text, this));
+ return true;
+ }
+ return false;
+ }
+
+ public boolean failViewItem(String text) {
+ if(viewItemAttemptID != null && !viewItemAttemptID.isEmpty()) {
+ if(System.currentTimeMillis() - viewItemAttemptTime < 500) {
+ return displayGuiItemRecipe(viewItemAttemptID, text);
+ }
+ }
+ return false;
+ }
+
+ public File getWebFile(String url) {
+ File f = new File(configLocation, "tmp/"+Base64.getEncoder().encodeToString(url.getBytes())+".html");
+ if(f.exists()) {
+ return f;
+ }
+
+ try {
+ f.getParentFile().mkdirs();
+ f.createNewFile();
+ f.deleteOnExit();
+ } catch (IOException e) {
+ return null;
+ }
+ try (BufferedInputStream inStream = new BufferedInputStream(new URL(url+"?action=raw&templates=expand").openStream());
+ FileOutputStream fileOutputStream = new FileOutputStream(f)) {
+ byte dataBuffer[] = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = inStream.read(dataBuffer, 0, 1024)) != -1) {
+ fileOutputStream.write(dataBuffer, 0, bytesRead);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ return f;
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java
new file mode 100644
index 00000000..e60ca14f
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java
@@ -0,0 +1,1704 @@
+package io.github.moulberry.notenoughupdates;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import io.github.moulberry.notenoughupdates.infopanes.*;
+import io.github.moulberry.notenoughupdates.itemeditor.NEUItemEditor;
+import io.github.moulberry.notenoughupdates.util.LerpingFloat;
+import io.github.moulberry.notenoughupdates.util.LerpingInteger;
+import io.github.moulberry.notenoughupdates.util.TexLoc;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.*;
+import net.minecraft.client.gui.inventory.GuiContainer;
+import net.minecraft.client.renderer.*;
+import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.client.renderer.entity.RenderManager;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.client.resources.model.IBakedModel;
+import net.minecraft.client.shader.Framebuffer;
+import net.minecraft.client.shader.Shader;
+import net.minecraft.client.shader.ShaderGroup;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityList;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.init.Items;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.Matrix4f;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.world.World;
+import org.apache.commons.lang3.StringUtils;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.NumberFormat;
+import java.util.List;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static io.github.moulberry.notenoughupdates.GuiTextures.*;
+
+public class NEUOverlay extends Gui {
+
+ private NEUManager manager;
+
+ private String mobRegex = ".*?((_MONSTER)|(_ANIMAL)|(_MINIBOSS)|(_BOSS)|(_SC))$";
+ private String petRegex = ".*?;[0-4]$";
+
+ private ResourceLocation[] sortIcons = new ResourceLocation[] {
+ sort_all, sort_mob, sort_pet, sort_tool, sort_armor, sort_accessory
+ };
+ private ResourceLocation[] sortIconsActive = new ResourceLocation[] {
+ sort_all_active, sort_mob_active, sort_pet_active, sort_tool_active, sort_armor_active, sort_accessory_active
+ };
+
+ private ResourceLocation[] orderIcons = new ResourceLocation[] {
+ order_alphabetical, order_rarity
+ };
+ private ResourceLocation[] orderIconsActive = new ResourceLocation[] {
+ order_alphabetical_active, order_rarity_active
+ };
+
+ private int searchBarXSize = 200;
+ private final int searchBarYOffset = 10;
+ private final int searchBarYSize = 40;
+ private final int searchBarPadding = 2;
+
+ public static final int BOX_PADDING = 15;
+ public static final int ITEM_PADDING = 4;
+ public static final int ITEM_SIZE = 16;
+
+ private Color bg = new Color(90, 90, 140, 50);
+ private Color fg = new Color(100,100,100, 255);
+
+ //private String informationPaneTitle;
+ //private ResourceLocation informationPaneImage = null;
+ //private String[] informationPane;
+ //private AtomicInteger webpageAwaitID = new AtomicInteger(-1);
+ //private boolean configOpen = false;
+ private InfoPane activeInfoPane = null;
+
+ private TreeSet<JsonObject> searchedItems = null;
+ private JsonObject[] searchedItemsArr = null;
+
+ private boolean itemPaneOpen = false;
+ private boolean hoveringItemPaneToggle = false;
+
+ private int page = 0;
+
+ private LerpingFloat itemPaneOffsetFactor = new LerpingFloat(1);
+ private LerpingInteger itemPaneTabOffset = new LerpingInteger(20, 50);
+ private LerpingFloat infoPaneOffsetFactor = new LerpingFloat(0);
+
+ private boolean searchMode = false;
+ private long millisLastLeftClick = 0;
+
+ boolean mouseDown = false;
+
+ private boolean redrawItems = false;
+
+ private boolean searchBarHasFocus = false;
+ GuiTextField textField = new GuiTextField(0, null, 0, 0, 0, 0);
+
+ private static final int COMPARE_MODE_ALPHABETICAL = 0;
+ private static final int COMPARE_MODE_RARITY = 1;
+
+ private static final int SORT_MODE_ALL = 0;
+ private static final int SORT_MODE_MOB = 1;
+ private static final int SORT_MODE_PET = 2;
+ private static final int SORT_MODE_TOOL = 3;
+ private static final int SORT_MODE_ARMOR = 4;
+ private static final int SORT_MODE_ACCESSORY = 5;
+
+ private ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+
+ public NEUOverlay(NEUManager manager) {
+ this.manager = manager;
+ textField.setFocused(true);
+ textField.setCanLoseFocus(false);
+ }
+
+ public void reset() {
+ searchBarHasFocus = false;
+ if(!(searchMode || (manager.config.keepopen.value && itemPaneOpen))) {
+ itemPaneOpen = false;
+ itemPaneOffsetFactor.setValue(1);
+ itemPaneTabOffset.setValue(20);
+ }
+ }
+
+ public void showInfo(JsonObject item) {
+ if(item.has("info") && item.has("infoType")) {
+ JsonArray lore = item.get("info").getAsJsonArray();
+ String[] loreA = new String[lore.size()];
+ for (int i = 0; i < lore.size(); i++) loreA[i] = lore.get(i).getAsString();
+ String loreS = StringUtils.join(loreA, "\n");
+
+ String name = item.get("displayname").getAsString();
+ switch(item.get("infoType").getAsString()) {
+ case "WIKI_URL":
+ displayInformationPane(HTMLInfoPane.createFromWikiUrl(this, manager, name, loreS));
+ return;
+ case "WIKI":
+ displayInformationPane(HTMLInfoPane.createFromWiki(this, manager, name, loreS));
+ return;
+ case "HTML":
+ displayInformationPane(new HTMLInfoPane(this, manager, name, loreS));
+ return;
+ }
+ displayInformationPane(new TextInfoPane(this, manager, name, loreS));
+ }
+ }
+
+ /**
+ * Handles the mouse input, cancelling the forge event if a NEU gui element is clicked.
+ */
+ public boolean mouseInput() {
+ int width = scaledresolution.getScaledWidth();
+ int height = scaledresolution.getScaledHeight();
+
+ int mouseX = Mouse.getX() / scaledresolution.getScaleFactor();
+ int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor();
+
+ if(Mouse.getEventButtonState()) {
+ mouseDown = true;
+ } else if(Mouse.getEventButton() != -1) {
+ mouseDown = false;
+ }
+
+ //Unfocuses the search bar by default. Search bar is focused if the click is on the bar itself.
+ if(Mouse.getEventButtonState()) setSearchBarFocus(false);
+
+ //Item selection (right) gui
+ if(mouseX > width*getItemPaneOffsetFactor()) {
+ if(!Mouse.getEventButtonState()) return true; //End early if the mouse isn't pressed, but still cancel event.
+
+ AtomicBoolean clickedItem = new AtomicBoolean(false);
+ iterateItemSlots(new ItemSlotConsumer() {
+ public void consume(int x, int y, int id) {
+ if(mouseX >= x-1 && mouseX <= x+ITEM_SIZE+1) {
+ if(mouseY >= y-1 && mouseY <= y+ITEM_SIZE+1) {
+ clickedItem.set(true);
+
+ JsonObject item = getSearchedItemPage(id);
+ if (item != null) {
+ if(Mouse.getEventButton() == 0) {
+ manager.showRecipe(item);
+ } else if(Mouse.getEventButton() == 1) {
+ showInfo(item);
+ } else if(Mouse.getEventButton() == 2) {
+ textField.setText("id:"+item.get("internalname").getAsString());
+ updateSearch();
+ searchMode = true;
+ }
+ }
+ }
+ }
+ }
+ });
+ if(!clickedItem.get()) {
+ int paneWidth = (int)(width/3*getWidthMult());
+ int leftSide = (int)(width*getItemPaneOffsetFactor());
+ int rightSide = leftSide+paneWidth-BOX_PADDING-getItemBoxXPadding();
+ leftSide = leftSide+BOX_PADDING+getItemBoxXPadding();
+
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+ int maxPages = getMaxPages();
+ String name = scaledresolution.getScaleFactor()<4?"Page: ":"";
+ float maxStrLen = fr.getStringWidth(EnumChatFormatting.BOLD+name + maxPages + "/" + maxPages);
+ float maxButtonXSize = (rightSide-leftSide+2 - maxStrLen*0.5f - 10)/2f;
+ int buttonXSize = (int)Math.min(maxButtonXSize, getSearchBarYSize()*480/160f);
+ int ySize = (int)(buttonXSize/480f*160);
+ int yOffset = (int)((getSearchBarYSize()-ySize)/2f);
+ int top = BOX_PADDING+yOffset;
+
+ if(mouseY >= top && mouseY <= top+ySize) {
+ int leftPrev = leftSide-1;
+ if(mouseX > leftPrev && mouseX < leftPrev+buttonXSize) { //"Previous" button
+ setPage(page-1);
+ }
+ int leftNext = rightSide+1-buttonXSize;
+ if(mouseX > leftNext && mouseX < leftNext+buttonXSize) { //"Next" button
+ setPage(page+1);
+ }
+ }
+
+ float sortIconsMinX = (sortIcons.length+orderIcons.length)*(ITEM_SIZE+ITEM_PADDING)+ITEM_SIZE;
+ float availableX = rightSide-(leftSide+BOX_PADDING+getItemBoxXPadding());
+ float sortOrderScaleFactor = Math.min(1, availableX / sortIconsMinX);
+
+ int scaledITEM_SIZE = (int)(ITEM_SIZE*sortOrderScaleFactor);
+ int scaledItemPaddedSize = (int)((ITEM_SIZE+ITEM_PADDING)*sortOrderScaleFactor);
+ int iconTop = height-BOX_PADDING-(ITEM_SIZE+scaledITEM_SIZE)/2-1;
+
+ if(mouseY >= iconTop && mouseY <= iconTop+scaledITEM_SIZE) {
+ for(int i=0; i<orderIcons.length; i++) {
+ int orderIconX = leftSide+BOX_PADDING+getItemBoxXPadding()+i*scaledItemPaddedSize;
+ if(mouseX >= orderIconX && mouseX <= orderIconX+scaledITEM_SIZE) {
+ if(Mouse.getEventButton() == 0) {
+ manager.config.compareMode.value = new Double(i);
+ updateSearch();
+ } else if(Mouse.getEventButton() == 1) {
+ manager.config.compareAscending.value.set(i, !manager.config.compareAscending.value.get(i));
+ updateSearch();
+ }
+ }
+ }
+
+ for(int i=0; i<sortIcons.length; i++) {
+ int sortIconX = rightSide-scaledITEM_SIZE-i*scaledItemPaddedSize;
+ if(mouseX >= sortIconX && mouseX <= sortIconX+scaledITEM_SIZE) {
+ manager.config.sortMode.value = new Double(i);
+ updateSearch();
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ if(Mouse.getEventButton() == 2) {
+ Slot slot = Utils.getSlotUnderMouse((GuiContainer)Minecraft.getMinecraft().currentScreen);
+ if(slot != null) {
+ ItemStack hover = slot.getStack();
+ if(hover != null) {
+ textField.setText("id:"+manager.getInternalNameForItem(hover));
+ updateSearch();
+ searchMode = true;
+ return true;
+ }
+ }
+ }
+
+ //Clicking on "close info pane" button
+ if(mouseX > width*getInfoPaneOffsetFactor()-22 && mouseX < width*getInfoPaneOffsetFactor()-6) {
+ if(mouseY > 7 && mouseY < 23) {
+ if(Mouse.getEventButtonState() && Mouse.getEventButton() < 2) { //Left or right click up
+ displayInformationPane(null);
+ return true;
+ }
+ }
+ }
+
+ //Search bar
+ if(mouseX >= width/2 - getSearchBarXSize()/2 && mouseX <= width/2 + getSearchBarXSize()/2) {
+ if(mouseY >= height - searchBarYOffset - getSearchBarYSize() &&
+ mouseY <= height - searchBarYOffset) {
+ if(Mouse.getEventButtonState()) {
+ setSearchBarFocus(true);
+ if(Mouse.getEventButton() == 1) { //Right mouse button down
+ textField.setText("");
+ updateSearch();
+ } else {
+ if(System.currentTimeMillis() - millisLastLeftClick < 300) {
+ searchMode = !searchMode;
+ }
+ textField.setCursorPosition(getClickedIndex(mouseX, mouseY));
+ millisLastLeftClick = System.currentTimeMillis();
+ }
+ }
+ return true;
+ }
+ }
+
+ int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor();
+ int topTextBox = height - searchBarYOffset - getSearchBarYSize();
+ int iconSize = getSearchBarYSize()+paddingUnscaled*2;
+ if(paddingUnscaled < 1) paddingUnscaled = 1;
+
+ if(mouseY > topTextBox - paddingUnscaled && mouseY < topTextBox - paddingUnscaled + iconSize) {
+ if(mouseX > width/2 + getSearchBarXSize()/2 + paddingUnscaled*6 &&
+ mouseX < width/2 + getSearchBarXSize()/2 + paddingUnscaled*6 + iconSize) {
+ if(Mouse.getEventButtonState()) {
+ displayInformationPane(HTMLInfoPane.createFromWikiUrl(this, manager, "Help",
+ "https://moulberry.github.io/files/neu_help.html"));
+ }
+ } else if(mouseX > width/2 - getSearchBarXSize()/2 - paddingUnscaled*6 - iconSize &&
+ mouseX < width/2 - getSearchBarXSize()/2 - paddingUnscaled*6) {
+ if(Mouse.getEventButtonState()) {
+ if(activeInfoPane instanceof SettingsInfoPane) {
+ displayInformationPane(null);
+ } else {
+ displayInformationPane(new SettingsInfoPane(this, manager));
+ }
+ }
+ }
+ }
+
+ if(activeInfoPane != null) {
+ if(mouseX < width*getInfoPaneOffsetFactor()) {
+ activeInfoPane.mouseInput(width, height, mouseX, mouseY, mouseDown);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public int getSearchBarXSize() {
+ if(scaledresolution.getScaleFactor()==4) return (int)(searchBarXSize*0.8);
+ return searchBarXSize;
+ }
+
+ public void displayInformationPane(InfoPane pane) {
+ if(pane == null) {
+ infoPaneOffsetFactor.setTarget(0);
+ } else {
+ infoPaneOffsetFactor.setTarget(1/3f);
+ }
+ infoPaneOffsetFactor.resetTimer();
+ this.activeInfoPane = pane;
+ }
+
+ public InfoPane getActiveInfoPane() {
+ return activeInfoPane;
+ }
+
+ /*public void displayInformationPane(String title, String infoType, String[] info) {
+ scrollHeight.setValue(0);
+ informationPaneTitle = title;
+ informationPaneImage = null;
+ informationPane = null;
+
+ configOpen = false;
+
+ infoPaneOffsetFactor.setTarget(1/3f);
+ infoPaneOffsetFactor.resetTimer();
+
+ webpageAwaitID.incrementAndGet();
+
+ if(info == null || info.length == 0) {
+ informationPane = new String[]{"\u00A77No additional information."};
+ } else {
+ String joined = StringUtils.join(info, "\n");
+ String wiki = null;
+ String html = null;
+ if(infoType.equals("TEXT")) {
+ informationPane = info;
+ return;
+ } else if(infoType.equals("WIKI_URL")) {
+ File f = manager.getWebFile(joined);
+ if(f == null) {
+ informationPane = new String[] { EnumChatFormatting.RED+"Failed to load wiki url: "+joined };
+ return;
+ };
+
+ StringBuilder sb = new StringBuilder();
+ try(BufferedReader br = new BufferedReader(new InputStreamReader(
+ new FileInputStream(f), StandardCharsets.UTF_8))) {
+ String l;
+ while((l = br.readLine()) != null){
+ sb.append(l).append("\n");
+ }
+ } catch(IOException e) {
+ informationPane = new String[] { EnumChatFormatting.RED+"Failed to load wiki url: "+joined };
+ return;
+ }
+ wiki = sb.toString();
+ }
+
+ if(infoType.equals("WIKI") || wiki != null) {
+ if(wiki == null) wiki = joined;
+ try {
+ String[] split = wiki.split("</infobox>");
+ wiki = split[split.length - 1]; //Remove everything before infobox
+ wiki = wiki.split("<span class=\"navbox-vde\">")[0]; //Remove navbox
+ wiki = wiki.split("<table class=\"navbox mw-collapsible\"")[0];
+ wiki = "__NOTOC__\n" + wiki; //Remove TOC
+ try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/parsed.txt"))) {
+ out.println(wiki);
+ } catch (IOException e) {
+ }
+ html = wikiModel.render(wiki);
+ try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/html.txt"))) {
+ out.println(html);
+ } catch (IOException e) {
+ }
+ } catch (Exception e) {
+ informationPane = new String[]{EnumChatFormatting.RED + "Failed to parse wiki: " + joined};
+ return;
+ }
+ }
+
+ if(infoType.equals("HTML") || html != null) {
+ if(html == null) html = joined;
+ processAndSetWebpageImage(html, title);
+ }
+ }
+ }*/
+
+ public int getClickedIndex(int mouseX, int mouseY) {
+ int width = scaledresolution.getScaledWidth();
+ int height = scaledresolution.getScaledHeight();
+
+ int xComp = mouseX - (width/2 - getSearchBarXSize()/2 + 5);
+
+ String trimmed = Minecraft.getMinecraft().fontRendererObj.trimStringToWidth(textField.getText(), xComp);
+ int linePos = trimmed.length();
+ if(linePos != textField.getText().length()) {
+ char after = textField.getText().charAt(linePos);
+ int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed);
+ int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after);
+ if(trimmedWidth + charWidth/2 < xComp-5) {
+ linePos++;
+ }
+ }
+ return linePos;
+ }
+
+ public void setSearchBarFocus(boolean focus) {
+ if(focus) {
+ itemPaneOpen = true;
+ }
+ searchBarHasFocus = focus;
+ }
+
+ /**
+ * Handles the keyboard input, cancelling the forge event if the search bar has focus.
+ */
+ public boolean keyboardInput(boolean hoverInv) {
+ if(Minecraft.getMinecraft().currentScreen == null) return false;
+ Keyboard.enableRepeatEvents(true);
+
+ if(Keyboard.isKeyDown(Keyboard.KEY_Y)) {
+ displayInformationPane(new DevInfoPane(this, manager));
+ //displayInformationPane(new QOLInfoPane(this, manager));
+ }
+
+ if(Keyboard.getEventKeyState()) {
+ if(searchBarHasFocus) {
+ if(Keyboard.getEventKey() == 1) {
+ searchBarHasFocus = false;
+ } else {
+ if(textField.textboxKeyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey())) {
+ updateSearch();
+ }
+ }
+ } else {
+ if(activeInfoPane != null) {
+ activeInfoPane.keyboardInput();
+ }
+
+ AtomicReference<String> internalname = new AtomicReference<>(null);
+ AtomicReference<ItemStack> itemstack = new AtomicReference<>(null);
+ Slot slot = Utils.getSlotUnderMouse((GuiContainer)Minecraft.getMinecraft().currentScreen);
+ if(slot != null) {
+ ItemStack hover = slot.getStack();
+ if(hover != null) {
+ internalname.set(manager.getInternalNameForItem(hover));
+ itemstack.set(hover);
+ }
+ } else if(!hoverInv) {
+ int height = scaledresolution.getScaledHeight();
+
+ int mouseX = Mouse.getX() / scaledresolution.getScaleFactor();
+ int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor();
+
+ iterateItemSlots(new ItemSlotConsumer() {
+ public void consume(int x, int y, int id) {
+ if (mouseX >= x - 1 && mouseX <= x + ITEM_SIZE + 1) {
+ if (mouseY >= y - 1 && mouseY <= y + ITEM_SIZE + 1) {
+ JsonObject json = getSearchedItemPage(id);
+ if (json != null) internalname.set(json.get("internalname").getAsString());
+ }
+ }
+ }
+ });
+ }
+ if(internalname.get() != null) {
+ if(itemstack.get() != null) {
+ if(manager.config.enableItemEditing.value && Keyboard.getEventCharacter() == 'k') {
+ Minecraft.getMinecraft().displayGuiScreen(new NEUItemEditor(manager,
+ internalname.get(), manager.getJsonForItem(itemstack.get())));
+ return true;
+ }
+ }
+ JsonObject item = manager.getItemInformation().get(internalname.get());
+ if(item != null) {
+ if(Keyboard.getEventCharacter() == 'u') {
+ manager.displayGuiItemUsages(internalname.get(), "");
+ return true;
+ } else if(Keyboard.getEventCharacter() == 'f') {
+ toggleRarity(item.get("internalname").getAsString());
+ return true;
+ } else if(Keyboard.getEventCharacter() == 'r') {
+ manager.showRecipe(item);
+ return true;
+ } else if(Keyboard.getEventCharacter() == 'l') {
+ if(Minecraft.getMinecraft().thePlayer.capabilities.isCreativeMode) {
+ Minecraft.getMinecraft().thePlayer.inventory.addItemStackToInventory(
+ manager.jsonToStack(item));
+ }
+ } else if(manager.config.enableItemEditing.value && Keyboard.getEventCharacter() == 'k') {
+ Minecraft.getMinecraft().displayGuiScreen(new NEUItemEditor(manager,
+ internalname.get(), item));
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return searchBarHasFocus; //Cancels keyboard events if the search bar has focus
+ }
+
+ public void toggleRarity(String internalname) {
+ if(getFavourites().contains(internalname)) {
+ getFavourites().remove(internalname);
+ } else {
+ getFavourites().add(internalname);
+ }
+ updateSearch();
+ }
+
+ String[] rarityArr = new String[] {
+ EnumChatFormatting.WHITE+EnumChatFormatting.BOLD.toString()+"COMMON",
+ EnumChatFormatting.GREEN+EnumChatFormatting.BOLD.toString()+"UNCOMMON",
+ EnumChatFormatting.BLUE+EnumChatFormatting.BOLD.toString()+"RARE",
+ EnumChatFormatting.DARK_PURPLE+EnumChatFormatting.BOLD.toString()+"EPIC",
+ EnumChatFormatting.GOLD+EnumChatFormatting.BOLD.toString()+"LEGENDARY",
+ EnumChatFormatting.LIGHT_PURPLE+EnumChatFormatting.BOLD.toString()+"SPECIAL",
+ };
+ public int getRarity(JsonArray lore) {
+ for(int i=lore.size()-1; i>=0; i--) {
+ String line = lore.get(i).getAsString();
+
+ for(int j=0; j<rarityArr.length; j++) {
+ if(line.startsWith(rarityArr[j])) {
+ return j;
+ }
+ }
+ }
+ return -1;
+ }
+
+ private int getCompareMode() {
+ return manager.config.compareMode.value.intValue();
+ }
+ private int getSortMode() {
+ return manager.config.sortMode.value.intValue();
+ }
+ private List<Boolean> getCompareAscending() {
+ return manager.config.compareAscending.value;
+ }
+ private List<String> getFavourites() {
+ return manager.config.favourites.value;
+ }
+
+ private Comparator<JsonObject> getItemComparator() {
+ return (o1, o2) -> {
+ //1 (mult) if o1 should appear after o2
+ //-1 (-mult) if o2 should appear after o1
+ if(getFavourites().contains(o1.get("internalname").getAsString()) && !getFavourites().contains(o2.get("internalname").getAsString())) {
+ return -1;
+ }
+ if(!getFavourites().contains(o1.get("internalname").getAsString()) && getFavourites().contains(o2.get("internalname").getAsString())) {
+ return 1;
+ }
+
+ int mult = getCompareAscending().get(getCompareMode()) ? 1 : -1;
+ if(getCompareMode() == COMPARE_MODE_RARITY) {
+ int rarity1 = getRarity(o1.get("lore").getAsJsonArray());
+ int rarity2 = getRarity(o2.get("lore").getAsJsonArray());
+
+ if(rarity1 < rarity2) return mult;
+ if(rarity1 > rarity2) return -mult;
+ }
+
+ String i1 = o1.get("internalname").getAsString();
+ String[] split1 = i1.split("_");
+ String last1 = split1[split1.length-1];
+ String start1 = i1.substring(0, i1.length()-last1.length());
+
+ String i2 = o2.get("internalname").getAsString();
+ String[] split2 = i2.split("_");
+ String last2 = split2[split2.length-1];
+ String start2 = i2.substring(0, i2.length()-last2.length());
+
+ mult = getCompareAscending().get(COMPARE_MODE_ALPHABETICAL) ? 1 : -1;
+ if(start1.equals(start2)) {
+ String[] order = new String[]{"HELMET","CHESTPLATE","LEGGINGS","BOOTS"};
+ int type1 = checkItemType(o1.get("lore").getAsJsonArray(), order);
+ int type2 = checkItemType(o2.get("lore").getAsJsonArray(), order);
+
+
+ if(type1 < type2) return -mult;
+ if(type1 > type2) return mult;
+ }
+
+ int nameComp = mult*o1.get("displayname").getAsString().replaceAll("(?i)\\u00A7.", "")
+ .compareTo(o2.get("displayname").getAsString().replaceAll("(?i)\\u00A7.", ""));
+ if(nameComp != 0) {
+ return nameComp;
+ }
+ return mult*o1.get("internalname").getAsString().compareTo(o2.get("internalname").getAsString());
+ };
+ }
+
+ public int checkItemType(JsonArray lore, String... typeMatches) {
+ for(int i=lore.size()-1; i>=0; i--) {
+ String line = lore.get(i).getAsString();
+
+ for(String rarity : rarityArr) {
+ for(int j=0; j<typeMatches.length; j++) {
+ if(line.trim().equals(rarity + " " + typeMatches[j])) {
+ return j;
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ public boolean checkMatchesSort(String internalname, JsonObject item) {
+ if(getSortMode() == SORT_MODE_ALL) {
+ return !internalname.matches(mobRegex);
+ } else if(getSortMode() == SORT_MODE_MOB) {
+ return internalname.matches(mobRegex);
+ } else if(getSortMode() == SORT_MODE_PET) {
+ return internalname.matches(petRegex);
+ } else if(getSortMode() == SORT_MODE_TOOL) {
+ return checkItemType(item.get("lore").getAsJsonArray(),
+ "SWORD", "BOW", "AXE", "PICKAXE", "FISHING ROD", "WAND", "SHOVEL", "HOE") >= 0;
+ } else if(getSortMode() == SORT_MODE_ARMOR) {
+ return checkItemType(item.get("lore").getAsJsonArray(), "HELMET", "CHESTPLATE", "LEGGINGS", "BOOTS") >= 0;
+ } else if(getSortMode() == SORT_MODE_ACCESSORY) {
+ return checkItemType(item.get("lore").getAsJsonArray(), "ACCESSORY") >= 0;
+ }
+ return true;
+ }
+
+ public void updateSearch() {
+ if(searchedItems==null) searchedItems = new TreeSet<>(getItemComparator());
+ searchedItems.clear();
+ searchedItemsArr = null;
+ redrawItems = true;
+ Set<String> itemsMatch = manager.search(textField.getText());
+ for(String itemname : itemsMatch) {
+ JsonObject item = manager.getItemInformation().get(itemname);
+ if(checkMatchesSort(itemname, item)) {
+ searchedItems.add(item);
+ }
+ }
+ }
+
+ public JsonObject[] getSearchedItems() {
+ if(searchedItems==null) {
+ updateSearch();
+ }
+ if(searchedItemsArr==null) {
+ searchedItemsArr = new JsonObject[searchedItems.size()];
+ int i=0;
+ for(JsonObject item : searchedItems) {
+ searchedItemsArr[i] = item;
+ i++;
+ }
+ }
+ return searchedItemsArr;
+ }
+
+ public JsonObject getSearchedItemPage(int index) {
+ if(index < getSlotsXSize()*getSlotsYSize()) {
+ int actualIndex = index + getSlotsXSize()*getSlotsYSize()*page;
+ if(actualIndex < getSearchedItems().length) {
+ return getSearchedItems()[actualIndex];
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public int getItemBoxXPadding() {
+ int width = scaledresolution.getScaledWidth();
+ return (((int)(width/3*getWidthMult())-2*BOX_PADDING)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2;
+ }
+
+ private abstract class ItemSlotConsumer {
+ public abstract void consume(int x, int y, int id);
+ }
+
+ public void iterateItemSlots(ItemSlotConsumer itemSlotConsumer) {
+ int width = scaledresolution.getScaledWidth();
+ int itemBoxXPadding = getItemBoxXPadding();
+ iterateItemSlots(itemSlotConsumer, (int)(width*getItemPaneOffsetFactor())+BOX_PADDING+itemBoxXPadding);
+ }
+
+ /**
+ * Iterates through all the item slots in the right panel and calls a ItemSlotConsumer for each slot with
+ * arguments equal to the slot's x and y position respectively. This is used in order to prevent
+ * code duplication issues.
+ */
+ public void iterateItemSlots(ItemSlotConsumer itemSlotConsumer, int xStart) {
+ int width = scaledresolution.getScaledWidth();
+ int height = scaledresolution.getScaledHeight();
+
+ int paneWidth = (int)(width/3*getWidthMult());
+ int itemBoxYPadding = ((height-getSearchBarYSize()-2*BOX_PADDING-ITEM_SIZE-2)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2;
+
+ int yStart = BOX_PADDING+getSearchBarYSize()+itemBoxYPadding;
+ int itemBoxXPadding = getItemBoxXPadding();
+ int xEnd = xStart+paneWidth-BOX_PADDING*2-ITEM_SIZE-itemBoxXPadding;
+ int yEnd = height-BOX_PADDING-ITEM_SIZE-2-itemBoxYPadding;
+
+ //Render the items, displaying the tooltip if the cursor is over the item
+ int id = 0;
+ for(int y = yStart; y < yEnd; y+=ITEM_SIZE+ITEM_PADDING) {
+ for(int x = xStart; x < xEnd; x+=ITEM_SIZE+ITEM_PADDING) {
+ itemSlotConsumer.consume(x, y, id++);
+ }
+ }
+ }
+
+ public float getWidthMult() {
+ float scaleFMult = 1;
+ if(scaledresolution.getScaleFactor()==4) scaleFMult = 0.9f;
+ return (float)Math.max(0.5, Math.min(1.5, manager.config.paneWidthMult.value.floatValue()))*scaleFMult;
+ }
+
+ /**
+ * Calculates the number of horizontal item slots.
+ */
+ public int getSlotsXSize() {
+ int width = scaledresolution.getScaledWidth();
+
+ int paneWidth = (int)(width/3*getWidthMult());
+ int itemBoxXPadding = (((int)(width-width*getItemPaneOffsetFactor())-2*BOX_PADDING)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2;
+ int xStart = (int)(width*getItemPaneOffsetFactor())+BOX_PADDING+itemBoxXPadding;
+ int xEnd = (int)(width*getItemPaneOffsetFactor())+paneWidth-BOX_PADDING-ITEM_SIZE;
+
+ return (int)Math.ceil((xEnd - xStart)/((float)(ITEM_SIZE+ITEM_PADDING)));
+ }
+
+ /**
+ * Calculates the number of vertical item slots.
+ */
+ public int getSlotsYSize() {
+ int height = scaledresolution.getScaledHeight();
+
+ int itemBoxYPadding = ((height-getSearchBarYSize()-2*BOX_PADDING-ITEM_SIZE-2)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2;
+ int yStart = BOX_PADDING+getSearchBarYSize()+itemBoxYPadding;
+ int yEnd = height-BOX_PADDING-ITEM_SIZE-2-itemBoxYPadding;
+
+ return (int)Math.ceil((yEnd - yStart)/((float)(ITEM_SIZE+ITEM_PADDING)));
+ }
+
+ public int getMaxPages() {
+ if(getSearchedItems().length == 0) return 1;
+ return (int)Math.ceil(getSearchedItems().length/(float)getSlotsYSize()/getSlotsXSize());
+ }
+
+ /**
+ * Takes in the x and y coordinates of a slot and returns the id of that slot.
+ */
+ /*public int getSlotId(int x, int y) {
+ int width = scaledresolution.getScaledWidth();
+ int height = scaledresolution.getScaledHeight();
+
+ int itemBoxXPadding = (((int)(width-width*getItemPaneOffsetFactor())-2*BOX_PADDING)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2;
+ int itemBoxYPadding = ((height-getSearchBarYSize()-2*BOX_PADDING-ITEM_SIZE-2)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2;
+
+ int xStart = (int)(width*getItemPaneOffsetFactor())+BOX_PADDING+itemBoxXPadding;
+ int yStart = BOX_PADDING+getSearchBarYSize()+itemBoxYPadding;
+
+ int xIndex = (x-xStart)/(ITEM_SIZE+ITEM_PADDING);
+ int yIndex = (y-yStart)/(ITEM_SIZE+ITEM_PADDING);
+ return xIndex + yIndex*getSlotsXSize();
+ }*/
+
+ public int getSearchBarYSize() {
+ return Math.max(searchBarYSize/scaledresolution.getScaleFactor(), ITEM_SIZE);
+ }
+
+ public void renderNavElement(int leftSide, int rightSide, int maxPages, int page, String name) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ String pageText = EnumChatFormatting.BOLD+name + page + "/" + maxPages;
+
+ float maxStrLen = fr.getStringWidth(EnumChatFormatting.BOLD+name + maxPages + "/" + maxPages);
+ float maxButtonXSize = (rightSide-leftSide+2 - maxStrLen*0.5f - 10)/2f;
+ int buttonXSize = (int)Math.min(maxButtonXSize, getSearchBarYSize()*480/160f);
+ int ySize = (int)(buttonXSize/480f*160);
+ int yOffset = (int)((getSearchBarYSize()-ySize)/2f);
+ int top = BOX_PADDING+yOffset;
+
+ /*drawRect(leftSide-1, top,
+ rightSide+1,
+ top+ySize, fg.getRGB());*/
+
+ int leftPressed = 0;
+ int rightPressed = 0;
+
+ if(Mouse.isButtonDown(0) || Mouse.isButtonDown(1)) {
+ int height = scaledresolution.getScaledHeight();
+
+ int mouseX = Mouse.getX() / scaledresolution.getScaleFactor();
+ int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor();
+
+ if(mouseY >= top && mouseY <= top+ySize) {
+ int leftPrev = leftSide-1;
+ if(mouseX > leftPrev && mouseX < leftPrev+buttonXSize) { //"Previous" button
+ leftPressed = 1;
+ }
+ int leftNext = rightSide+1-buttonXSize;
+ if(mouseX > leftNext && mouseX < leftNext+buttonXSize) { //"Next" button
+ rightPressed = 1;
+ }
+ }
+ }
+
+ drawRect(leftSide-1, top, leftSide-1+buttonXSize, top+ySize, fg.getRGB());
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Minecraft.getMinecraft().getTextureManager().bindTexture(rightarrow);
+ Utils.drawTexturedRect(leftSide-1+leftPressed,
+ top+leftPressed,
+ buttonXSize, ySize, 1, 0, 0, 1);
+ Minecraft.getMinecraft().getTextureManager().bindTexture(rightarrow_overlay);
+ Utils.drawTexturedRect(leftSide-1,
+ top,
+ buttonXSize, ySize, 1-leftPressed, leftPressed, 1-leftPressed, leftPressed);
+ GlStateManager.bindTexture(0);
+ Utils.drawStringCenteredScaled(EnumChatFormatting.BOLD+"Prev", fr,
+ leftSide-1+buttonXSize*300/480f+leftPressed,
+ top+ySize/2f+leftPressed, false,
+ (int)(buttonXSize*240/480f), Color.BLACK.getRGB());
+
+ drawRect(rightSide+1-buttonXSize, top, rightSide+1, top+ySize, fg.getRGB());
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Minecraft.getMinecraft().getTextureManager().bindTexture(rightarrow);
+ Utils.drawTexturedRect(rightSide+1-buttonXSize+rightPressed,
+ top+rightPressed,
+ buttonXSize, ySize);
+ Minecraft.getMinecraft().getTextureManager().bindTexture(rightarrow_overlay);
+ Utils.drawTexturedRect(rightSide+1-buttonXSize,
+ top,
+ buttonXSize, ySize, 1-rightPressed, rightPressed, 1-rightPressed, rightPressed);
+ GlStateManager.bindTexture(0);
+ Utils.drawStringCenteredScaled(EnumChatFormatting.BOLD+"Next", fr,
+ rightSide+1-buttonXSize*300/480f+rightPressed,
+ top+ySize/2f+rightPressed, false,
+ (int)(buttonXSize*240/480f), Color.BLACK.getRGB());
+
+ int strMaxLen = rightSide-leftSide-2*buttonXSize-10;
+
+ drawRect(leftSide-1+buttonXSize+3, top, rightSide+1-buttonXSize-3, top+ySize,
+ new Color(177,177,177).getRGB());
+ drawRect(leftSide+buttonXSize+3, top+1, rightSide+1-buttonXSize-3, top+ySize,
+ new Color(50,50,50).getRGB());
+ drawRect(leftSide+buttonXSize+3, top+1, rightSide-buttonXSize-3, top+ySize-1, fg.getRGB());
+ Utils.drawStringCenteredScaledMaxWidth(pageText, fr,(leftSide+rightSide)/2,
+ top+ySize/2f, false, strMaxLen, Color.BLACK.getRGB());
+ }
+
+ private int limCol(int col) {
+ return Math.min(255, Math.max(0, col));
+ }
+
+ public float yaw = 0;
+ public float pitch = 20;
+
+ private void renderEntity(float posX, float posY, float scale, String name, Class<? extends EntityLivingBase>... classes) {
+ EntityLivingBase[] entities = new EntityLivingBase[classes.length];
+ try {
+ EntityLivingBase last = null;
+ for(int i=0; i<classes.length; i++) {
+ Class<? extends EntityLivingBase> clazz = classes[i];
+ if(clazz == null) continue;
+
+ EntityLivingBase newEnt = clazz.getConstructor(new Class[] {World.class}).newInstance(Minecraft.getMinecraft().theWorld);
+
+ //newEnt.renderYawOffset = yaw;
+ //newEnt.rotationYaw = yaw;
+ newEnt.rotationPitch = pitch;
+ //newEnt.rotationYawHead = yaw;
+ //newEnt.prevRotationYawHead = yaw-1;
+
+ newEnt.setCustomNameTag(name);
+
+ if(last != null) {
+ last.riddenByEntity = newEnt;
+ newEnt.ridingEntity = last;
+ last.updateRiderPosition();
+ }
+ last = newEnt;
+
+ entities[i] = newEnt;
+ }
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ e.printStackTrace();
+ return;
+ }
+
+
+ GlStateManager.enableColorMaterial();
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(posX, posY, 50.0F);
+ GlStateManager.scale(-scale, scale, scale);
+ GlStateManager.rotate(180.0F, 0.0F, 0.0F, 1.0F);
+
+ GlStateManager.rotate(135.0F, 0.0F, 1.0F, 0.0F);
+ RenderHelper.enableStandardItemLighting();
+ GlStateManager.rotate(-135.0F, 0.0F, 1.0F, 0.0F);
+
+ GlStateManager.rotate(pitch, 1.0F, 0.0F, 0.0F);
+ GlStateManager.rotate(yaw, 0.0F, 1.0F, 0.0F);
+
+ RenderManager rendermanager = Minecraft.getMinecraft().getRenderManager();
+ rendermanager.setPlayerViewY(180.0F);
+ rendermanager.setRenderShadow(false);
+ for(EntityLivingBase ent : entities) {
+ GL11.glColor4f(1,1,1,1);
+ if(ent != null) rendermanager.renderEntityWithPosYaw(ent, ent.posX, ent.posY, ent.posZ, 0.0F, 1.0F);
+ }
+ rendermanager.setRenderShadow(true);
+
+ GlStateManager.popMatrix();
+ RenderHelper.disableStandardItemLighting();
+ GlStateManager.disableRescaleNormal();
+ GlStateManager.setActiveTexture(OpenGlHelper.lightmapTexUnit);
+ GlStateManager.disableTexture2D();
+ GlStateManager.setActiveTexture(OpenGlHelper.defaultTexUnit);
+
+ GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
+ }
+
+ public void renderOverlay(int mouseX, int mouseY) {
+ if(searchMode && textField.getText().length() > 0) {
+ GuiContainer inv = (GuiContainer) Minecraft.getMinecraft().currentScreen;
+ int guiLeftI = (int)Utils.getField(GuiContainer.class, inv, "guiLeft", "field_147003_i");
+ int guiTopI = (int)Utils.getField(GuiContainer.class, inv, "guiTop", "field_147009_r");
+
+ GL11.glTranslatef(0, 0, 260);
+ int overlay = new Color(0, 0, 0, 100).getRGB();
+ for(Slot slot : inv.inventorySlots.inventorySlots) {
+ if(slot.getStack() == null || !manager.doesStackMatchSearch(slot.getStack(), textField.getText())) {
+ drawRect(guiLeftI+slot.xDisplayPosition, guiTopI+slot.yDisplayPosition,
+ guiLeftI+slot.xDisplayPosition+16, guiTopI+slot.yDisplayPosition+16,
+ overlay);
+ }
+ }
+ if(Utils.getSlotUnderMouse(inv) != null) {
+ ItemStack stack = Utils.getSlotUnderMouse(inv).getStack();
+ //Minecraft.getMinecraft().currentScreen.renderToolTip(stack, mouseX, mouseY);
+ Class<?>[] params = new Class[]{ItemStack.class, int.class, int.class};
+ Method renderToolTip = Utils.getMethod(GuiScreen.class, params, "renderToolTip", "func_146285_a");
+ if(renderToolTip != null) {
+ renderToolTip.setAccessible(true);
+ try {
+ renderToolTip.invoke(Minecraft.getMinecraft().currentScreen, stack, mouseX, mouseY);
+ } catch(Exception e) {}
+ }
+ }
+ GL11.glTranslatef(0, 0, -260);
+ }
+ }
+
+ Shader blurShaderHorz = null;
+ Framebuffer blurOutputHorz = null;
+ Shader blurShaderVert = null;
+ Framebuffer blurOutputVert = null;
+
+ private Matrix4f createProjectionMatrix(int width, int height) {
+ Matrix4f projMatrix = new Matrix4f();
+ projMatrix.setIdentity();
+ projMatrix.m00 = 2.0F / (float)width;
+ projMatrix.m11 = 2.0F / (float)(-height);
+ projMatrix.m22 = -0.0020001999F;
+ projMatrix.m33 = 1.0F;
+ projMatrix.m03 = -1.0F;
+ projMatrix.m13 = 1.0F;
+ projMatrix.m23 = -1.0001999F;
+ return projMatrix;
+ }
+
+ private double lastBgBlurFactor = 5;
+ private void blurBackground() {
+ int width = Minecraft.getMinecraft().displayWidth;
+ int height = Minecraft.getMinecraft().displayHeight;
+
+ if(manager.config.bgBlurFactor.value <= 0) return;
+
+ if(blurOutputHorz == null) {
+ blurOutputHorz = new Framebuffer(width, height, false);
+ blurOutputHorz.setFramebufferFilter(GL11.GL_NEAREST);
+ }
+ if(blurOutputVert == null) {
+ blurOutputVert = new Framebuffer(width, height, false);
+ blurOutputVert.setFramebufferFilter(GL11.GL_NEAREST);
+ }
+ if(blurOutputHorz.framebufferWidth != width || blurOutputHorz.framebufferHeight != height) {
+ blurOutputHorz.createBindFramebuffer(width, height);
+ blurShaderHorz.setProjectionMatrix(createProjectionMatrix(width, height));
+ Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
+ }
+ if(blurOutputVert.framebufferWidth != width || blurOutputVert.framebufferHeight != height) {
+ blurOutputVert.createBindFramebuffer(width, height);
+ blurShaderVert.setProjectionMatrix(createProjectionMatrix(width, height));
+ Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
+ }
+
+ if(blurShaderHorz == null) {
+ try {
+ blurShaderHorz = new Shader(Minecraft.getMinecraft().getResourceManager(), "blur",
+ Minecraft.getMinecraft().getFramebuffer(), blurOutputHorz);
+ blurShaderHorz.getShaderManager().getShaderUniform("BlurDir").set(1, 0);
+ blurShaderHorz.setProjectionMatrix(createProjectionMatrix(width, height));
+ } catch(Exception e) { }
+ }
+ if(blurShaderVert == null) {
+ try {
+ blurShaderVert = new Shader(Minecraft.getMinecraft().getResourceManager(), "blur",
+ blurOutputHorz, blurOutputVert);
+ blurShaderVert.getShaderManager().getShaderUniform("BlurDir").set(0, 1);
+ blurShaderVert.setProjectionMatrix(createProjectionMatrix(width, height));
+ } catch(Exception e) { }
+ }
+ if(blurShaderHorz != null && blurShaderVert != null) {
+ if(manager.config.bgBlurFactor.value != lastBgBlurFactor) {
+ lastBgBlurFactor = Math.max(0, Math.min(50, manager.config.bgBlurFactor.value));
+ blurShaderHorz.getShaderManager().getShaderUniform("Radius").set((float)lastBgBlurFactor);
+ blurShaderVert.getShaderManager().getShaderUniform("Radius").set((float)lastBgBlurFactor);
+ }
+ GL11.glPushMatrix();
+ blurShaderHorz.loadShader(0);
+ blurShaderVert.loadShader(0);
+ GlStateManager.enableDepth();
+ GL11.glPopMatrix();
+
+ Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
+ }
+ }
+
+ public void renderBlurredBackground(int width, int height, int x, int y, int blurWidth, int blurHeight) {
+ if(manager.config.bgBlurFactor.value <= 0) return;
+
+ int f = scaledresolution.getScaleFactor();
+ float uMin = x/(float)width;
+ float uMax = (x+blurWidth)/(float)width;
+ float vMin = y/(float)height;
+ float vMax = (y+blurHeight)/(float)height;
+
+ blurOutputVert.bindFramebufferTexture();
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ //Utils.setScreen(width*f, height*f, f);
+ Utils.drawTexturedRect(x, y, blurWidth, blurHeight, uMin, uMax, vMax, vMin);
+ //Utils.setScreen(width, height, f);
+ blurOutputVert.unbindFramebufferTexture();
+ }
+
+ int guiScaleLast = 0;
+
+ /**
+ * Renders the search bar, item selection (right) and item info (left) gui elements.
+ */
+ public void render(int mouseX, int mouseY, boolean hoverInv) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+ scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int width = scaledresolution.getScaledWidth();
+ int height = scaledresolution.getScaledHeight();
+
+ if(guiScaleLast != scaledresolution.getScaleFactor()) {
+ guiScaleLast = scaledresolution.getScaleFactor();
+ redrawItems = true;
+ }
+
+ blurBackground();
+
+ yaw++;
+ yaw %= 360;
+
+ manager.updatePrices();
+
+ int opacity = Math.min(255, Math.max(0, manager.config.bgOpacity.value.intValue()));
+ bg = new Color((bg.getRGB() & 0x00ffffff) | opacity << 24, true);
+
+ opacity = Math.min(255, Math.max(0, manager.config.fgOpacity.value.intValue()));
+ Color fgCustomOpacity = new Color((fg.getRGB() & 0x00ffffff) | opacity << 24, true);
+ Color fgFavourite = new Color(limCol(fg.getRed()+20), limCol(fg.getGreen()+10), limCol(fg.getBlue()-10), opacity);
+ Color fgFavourite2 = new Color(limCol(fg.getRed()+100), limCol(fg.getGreen()+50), limCol(fg.getBlue()-50), opacity);
+
+ if(itemPaneOpen) {
+ if(itemPaneTabOffset.getValue() == 0) {
+ if(itemPaneOffsetFactor.getTarget() != 2/3f) {
+ itemPaneOffsetFactor.setTarget(2/3f);
+ itemPaneOffsetFactor.resetTimer();
+ }
+ } else {
+ if(itemPaneTabOffset.getTarget() != 0) {
+ itemPaneTabOffset.setTarget(0);
+ itemPaneTabOffset.resetTimer();
+ }
+ }
+ } else {
+ if(itemPaneOffsetFactor.getValue() == 1) {
+ if(itemPaneTabOffset.getTarget() != 20) {
+ itemPaneTabOffset.setTarget(20);
+ itemPaneTabOffset.resetTimer();
+ }
+ } else {
+ if(itemPaneOffsetFactor.getTarget() != 1f) {
+ itemPaneOffsetFactor.setTarget(1f);
+ itemPaneOffsetFactor.resetTimer();
+ }
+ }
+ }
+
+ itemPaneOffsetFactor.tick();
+ itemPaneTabOffset.tick();
+ infoPaneOffsetFactor.tick();
+
+ if(page > getMaxPages()-1) setPage(getMaxPages()-1);
+ if(page < 0) setPage(0);
+
+ GlStateManager.disableLighting();
+
+ /**
+ * Search bar
+ */
+ int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor();
+ if(paddingUnscaled < 1) paddingUnscaled = 1;
+
+ int topTextBox = height - searchBarYOffset - getSearchBarYSize();
+
+ /*Minecraft.getMinecraft().getTextureManager().bindTexture(logo_bg);
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Utils.drawTexturedRect((width)/2-37,
+ height - searchBarYOffset - getSearchBarYSize()-30,
+ 74, 54);
+ GlStateManager.bindTexture(0);*/
+
+ //Search bar background
+ drawRect(width/2 - getSearchBarXSize()/2 - paddingUnscaled,
+ topTextBox - paddingUnscaled,
+ width/2 + getSearchBarXSize()/2 + paddingUnscaled,
+ height - searchBarYOffset + paddingUnscaled, searchMode ? Color.YELLOW.getRGB() : Color.WHITE.getRGB());
+ drawRect(width/2 - getSearchBarXSize()/2,
+ topTextBox,
+ width/2 + getSearchBarXSize()/2,
+ height - searchBarYOffset, Color.BLACK.getRGB());
+
+ /*Minecraft.getMinecraft().getTextureManager().bindTexture(logo_fg);
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Utils.drawTexturedRect((width)/2-37,
+ height - searchBarYOffset - getSearchBarYSize()-27,
+ 74, 54);
+ GlStateManager.bindTexture(0);*/
+
+ //Settings
+ int iconSize = getSearchBarYSize()+paddingUnscaled*2;
+ Minecraft.getMinecraft().getTextureManager().bindTexture(settings);
+ drawRect(width/2 - getSearchBarXSize()/2 - paddingUnscaled*6 - iconSize,
+ topTextBox - paddingUnscaled,
+ width/2 - getSearchBarXSize()/2 - paddingUnscaled*6,
+ topTextBox - paddingUnscaled + iconSize, Color.WHITE.getRGB());
+
+ drawRect(width/2 - getSearchBarXSize()/2 - paddingUnscaled*5 - iconSize,
+ topTextBox,
+ width/2 - getSearchBarXSize()/2 - paddingUnscaled*7,
+ topTextBox - paddingUnscaled*2 + iconSize, Color.GRAY.getRGB());
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Utils.drawTexturedRect(width/2 - getSearchBarXSize()/2 - paddingUnscaled*6 - iconSize, topTextBox - paddingUnscaled, iconSize, iconSize);
+ GlStateManager.bindTexture(0);
+
+ //Help
+ Minecraft.getMinecraft().getTextureManager().bindTexture(help);
+ drawRect(width/2 + getSearchBarXSize()/2 + paddingUnscaled*6,
+ topTextBox - paddingUnscaled,
+ width/2 + getSearchBarXSize()/2 + paddingUnscaled*6 + iconSize,
+ topTextBox - paddingUnscaled + iconSize, Color.WHITE.getRGB());
+
+ drawRect(width/2 + getSearchBarXSize()/2 + paddingUnscaled*7,
+ topTextBox,
+ width/2 + getSearchBarXSize()/2 + paddingUnscaled*5 + iconSize,
+ topTextBox - paddingUnscaled*2 + iconSize, Color.GRAY.getRGB());
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Utils.drawTexturedRect(width/2 + getSearchBarXSize()/2 + paddingUnscaled*7, topTextBox,
+ iconSize-paddingUnscaled*2, iconSize-paddingUnscaled*2);
+ GlStateManager.bindTexture(0);
+
+ //Search bar text
+ fr.drawString(textField.getText(), width/2 - getSearchBarXSize()/2 + 5,
+ topTextBox+(getSearchBarYSize()-8)/2, Color.WHITE.getRGB());
+
+ //Determines position of cursor. Cursor blinks on and off every 500ms.
+ if(searchBarHasFocus && System.currentTimeMillis()%1000>500) {
+ String textBeforeCursor = textField.getText().substring(0, textField.getCursorPosition());
+ int textBeforeCursorWidth = fr.getStringWidth(textBeforeCursor);
+ drawRect(width/2 - getSearchBarXSize()/2 + 5 + textBeforeCursorWidth,
+ topTextBox+(getSearchBarYSize()-8)/2-1,
+ width/2 - getSearchBarXSize()/2 + 5 + textBeforeCursorWidth+1,
+ topTextBox+(getSearchBarYSize()-8)/2+9, Color.WHITE.getRGB());
+ }
+
+ String selectedText = textField.getSelectedText();
+ if(!selectedText.isEmpty()) {
+ int selectionWidth = fr.getStringWidth(selectedText);
+
+ int leftIndex = Math.min(textField.getCursorPosition(), textField.getSelectionEnd());
+ String textBeforeSelection = textField.getText().substring(0, leftIndex);
+ int textBeforeSelectionWidth = fr.getStringWidth(textBeforeSelection);
+
+ drawRect(width/2 - getSearchBarXSize()/2 + 5 + textBeforeSelectionWidth,
+ topTextBox+(getSearchBarYSize()-8)/2-1,
+ width/2 - getSearchBarXSize()/2 + 5 + textBeforeSelectionWidth + selectionWidth,
+ topTextBox+(getSearchBarYSize()-8)/2+9, Color.LIGHT_GRAY.getRGB());
+
+ fr.drawString(selectedText,
+ width/2 - getSearchBarXSize()/2 + 5 + textBeforeSelectionWidth,
+ topTextBox+(getSearchBarYSize()-8)/2, Color.BLACK.getRGB());
+ }
+
+
+ /**
+ * Item selection (right) gui element rendering
+ */
+ int paneWidth = (int)(width/3*getWidthMult());
+ int leftSide = (int)(width*getItemPaneOffsetFactor());
+ int rightSide = leftSide+paneWidth-BOX_PADDING-getItemBoxXPadding();
+
+ //Tab
+
+ Minecraft.getMinecraft().getTextureManager().bindTexture(itemPaneTabArrow);
+ GlStateManager.color(1f, 1f, 1f, 0.3f);
+ Utils.drawTexturedRect(width-itemPaneTabOffset.getValue(), height/2 - 50, 20, 100);
+ GlStateManager.bindTexture(0);
+
+ if(mouseX > width-itemPaneTabOffset.getValue() && mouseY > height/2 - 50
+ && mouseY < height/2 + 50) {
+ if(!hoveringItemPaneToggle) {
+ itemPaneOpen = !itemPaneOpen;
+ hoveringItemPaneToggle = true;
+ }
+ } else {
+ hoveringItemPaneToggle = false;
+ }
+
+ //Atomic reference used so that below lambda doesn't complain about non-effectively-final variable
+ AtomicReference<JsonObject> tooltipToDisplay = new AtomicReference<>(null);
+
+ if(itemPaneOffsetFactor.getValue() < 1) {
+ renderBlurredBackground(width, height,
+ leftSide+BOX_PADDING-5, BOX_PADDING-5,
+ paneWidth-BOX_PADDING*2+10, height-BOX_PADDING*2+10);
+
+ drawRect(leftSide+BOX_PADDING-5, BOX_PADDING-5,
+ leftSide+paneWidth-BOX_PADDING+5, height-BOX_PADDING+5, bg.getRGB());
+
+ renderNavElement(leftSide+BOX_PADDING+getItemBoxXPadding(), rightSide, getMaxPages(), page+1,
+ scaledresolution.getScaleFactor()<4?"Page: ":"");
+
+ //Sort bar
+ drawRect(leftSide+BOX_PADDING+getItemBoxXPadding()-1,
+ height-BOX_PADDING-ITEM_SIZE-2,
+ rightSide+1,
+ height-BOX_PADDING, fgCustomOpacity.getRGB());
+
+ float sortIconsMinX = (sortIcons.length+orderIcons.length)*(ITEM_SIZE+ITEM_PADDING)+ITEM_SIZE;
+ float availableX = rightSide-(leftSide+BOX_PADDING+getItemBoxXPadding());
+ float sortOrderScaleFactor = Math.min(1, availableX / sortIconsMinX);
+
+ int scaledITEM_SIZE = (int)(ITEM_SIZE*sortOrderScaleFactor);
+ int scaledItemPaddedSize = (int)((ITEM_SIZE+ITEM_PADDING)*sortOrderScaleFactor);
+ int iconTop = height-BOX_PADDING-(ITEM_SIZE+scaledITEM_SIZE)/2-1;
+
+ for(int i=0; i<orderIcons.length; i++) {
+ int orderIconX = leftSide+BOX_PADDING+getItemBoxXPadding()+i*scaledItemPaddedSize;
+ drawRect(orderIconX, iconTop,scaledITEM_SIZE+orderIconX,iconTop+scaledITEM_SIZE, fg.getRGB());
+
+ Minecraft.getMinecraft().getTextureManager().bindTexture(getCompareMode() == i ? orderIconsActive[i] : orderIcons[i]);
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Utils.drawTexturedRect(orderIconX, iconTop, scaledITEM_SIZE, scaledITEM_SIZE,0, 1, 0, 1, GL11.GL_NEAREST);
+
+ Minecraft.getMinecraft().getTextureManager().bindTexture(getCompareAscending().get(i) ? ascending_overlay : descending_overlay);
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Utils.drawTexturedRect(orderIconX, iconTop, scaledITEM_SIZE, scaledITEM_SIZE,0, 1, 0, 1, GL11.GL_NEAREST);
+ GlStateManager.bindTexture(0);
+ }
+
+ for(int i=0; i<sortIcons.length; i++) {
+ int sortIconX = rightSide-scaledITEM_SIZE-i*scaledItemPaddedSize;
+ drawRect(sortIconX, iconTop,scaledITEM_SIZE+sortIconX,iconTop+scaledITEM_SIZE, fg.getRGB());
+ Minecraft.getMinecraft().getTextureManager().bindTexture(getSortMode() == i ? sortIconsActive[i] : sortIcons[i]);
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Utils.drawTexturedRect(sortIconX, iconTop, scaledITEM_SIZE, scaledITEM_SIZE, 0, 1, 0, 1, GL11.GL_NEAREST);
+ GlStateManager.bindTexture(0);
+ }
+
+ if(!hoverInv) {
+ iterateItemSlots(new ItemSlotConsumer() {
+ public void consume(int x, int y, int id) {
+ JsonObject json = getSearchedItemPage(id);
+ if (json == null) {
+ return;
+ }
+ if (mouseX > x - 1 && mouseX < x + ITEM_SIZE + 1) {
+ if (mouseY > y - 1 && mouseY < y + ITEM_SIZE + 1) {
+ tooltipToDisplay.set(json);
+ }
+ }
+ }
+ });
+ }
+
+ //Iterate through all item slots and display the appropriate item
+ int itemBoxXPadding = getItemBoxXPadding();
+ int xStart = (int)(width*getItemPaneOffsetFactor())+BOX_PADDING+itemBoxXPadding;
+
+ renderItemsFromImage(xStart, width, height);
+ renderEnchOverlay();
+
+ checkFramebufferSizes(width, height);
+
+ if(redrawItems || !manager.config.cacheRenderedItempane.value) {
+ renderItemsToImage(width, height, fgFavourite2, fgFavourite, fgCustomOpacity, true, true);
+ redrawItems = false;
+ }
+ }
+
+ /**
+ * Item info (left) gui element rendering
+ */
+
+ rightSide = (int)(width*getInfoPaneOffsetFactor());
+ leftSide = rightSide - paneWidth;
+
+ if(activeInfoPane != null) {
+ activeInfoPane.tick();
+ activeInfoPane.render(width, height, bg, fg, scaledresolution, mouseX, mouseY);
+
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Minecraft.getMinecraft().getTextureManager().bindTexture(close);
+ Utils.drawTexturedRect(rightSide-22, 7, 16, 16);
+ GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
+ }
+
+ //Render tooltip
+ JsonObject json = tooltipToDisplay.get();
+ if(json != null) {
+ List<String> text = new ArrayList<>();
+ text.add(json.get("displayname").getAsString());
+ JsonArray lore = json.get("lore").getAsJsonArray();
+ for(int i=0; i<lore.size(); i++) {
+ text.add(lore.get(i).getAsString());
+ }
+
+ JsonObject auctionInfo = manager.getItemAuctionInfo(json.get("internalname").getAsString());
+ JsonObject bazaarInfo = manager.getBazaarInfo(json.get("internalname").getAsString());
+
+ boolean hasAuctionPrice = auctionInfo != null;
+ boolean hasBazaarPrice = bazaarInfo != null;
+
+ NumberFormat format = NumberFormat.getInstance(Locale.US);
+
+ NEUManager.CraftInfo craftCost = manager.getCraftCost(json.get("internalname").getAsString());
+
+ if(hasAuctionPrice || hasBazaarPrice || craftCost.fromRecipe) text.add("");
+ if(hasBazaarPrice) {
+ int bazaarBuyPrice = (int)bazaarInfo.get("avg_buy").getAsFloat();
+ text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Buy: "+
+ EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(bazaarBuyPrice)+" coins");
+ int bazaarSellPrice = (int)bazaarInfo.get("avg_sell").getAsFloat();
+ text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Sell: "+
+ EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(bazaarSellPrice)+" coins");
+ if(manager.config.advancedPriceInfo.value) {
+ int bazaarInstantBuyPrice = (int)bazaarInfo.get("curr_buy").getAsFloat();
+ text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Insta-Buy: "+
+ EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(bazaarInstantBuyPrice)+" coins");
+ int bazaarInstantSellPrice = (int)bazaarInfo.get("curr_sell").getAsFloat();
+ text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Insta-Sell: "+
+ EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(bazaarInstantSellPrice)+" coins");
+ }
+ }
+ if(hasAuctionPrice) {
+ int auctionPrice = (int)(auctionInfo.get("price").getAsFloat() / auctionInfo.get("count").getAsFloat());
+ text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"AH Price: "+
+ EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(auctionPrice)+" coins");
+ }
+ if(craftCost.fromRecipe) {
+ text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Raw Craft Cost: "+
+ EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format((int)craftCost.craftCost)+" coins");
+ }
+
+ boolean hasClick = false;
+ boolean hasInfo = false;
+ if(json.has("clickcommand") && !json.get("clickcommand").getAsString().isEmpty()) {
+ hasClick = true;
+ }
+ if(json.has("info") && json.get("info").getAsJsonArray().size() > 0) {
+ hasInfo = true;
+ }
+
+ if(hasClick || hasInfo) text.add("");
+ if(hasClick) text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"LMB/R : View recipe!");
+ if(hasInfo) text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"RMB : View additional information!");
+
+ Utils.drawHoveringText(text, mouseX, mouseY, width, height, -1, fr);
+ }
+ }
+
+ public void redrawItems() {
+ redrawItems = true;
+ }
+
+ public void setPage(int page) {
+ this.page = page;
+ redrawItems = true;
+ }
+
+ private Framebuffer[] itemFramebuffers = new Framebuffer[2];
+
+ private void checkFramebufferSizes(int width, int height) {
+ int sw = width*scaledresolution.getScaleFactor();
+ int sh = height*scaledresolution.getScaleFactor();
+ for(int i=0; i<itemFramebuffers.length; i++) {
+ if(itemFramebuffers[i] == null || itemFramebuffers[i].framebufferWidth != sw || itemFramebuffers[i].framebufferHeight != sh) {
+ if(itemFramebuffers[i] == null) {
+ itemFramebuffers[i] = new Framebuffer(sw, sh, true);
+ } else {
+ itemFramebuffers[i].createBindFramebuffer(sw, sh);
+ }
+ itemFramebuffers[i].setFramebufferFilter(GL11.GL_NEAREST);
+ redrawItems = true;
+ }
+ }
+ }
+
+ private void prepareFramebuffer(Framebuffer buffer, int sw, int sh) {
+ buffer.framebufferClear();
+ buffer.bindFramebuffer(false);
+ GL11.glViewport(0, 0, sw, sh);
+ }
+ private void cleanupFramebuffer(Framebuffer buffer, int sw, int sh) {
+ buffer.unbindFramebuffer();
+ Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(true);
+ }
+
+
+ private void renderItemsToImage(int width, int height, Color fgFavourite2,
+ Color fgFavourite, Color fgCustomOpacity, boolean items, boolean entities) {
+ int sw = width*scaledresolution.getScaleFactor();
+ int sh = height*scaledresolution.getScaleFactor();
+
+ GL11.glPushMatrix();
+ prepareFramebuffer(itemFramebuffers[0], sw, sh);
+ renderItems(10, items, entities);
+ cleanupFramebuffer(itemFramebuffers[0], sw, sh);
+ GL11.glPopMatrix();
+
+ GL11.glPushMatrix();
+ prepareFramebuffer(itemFramebuffers[1], sw, sh);
+ renderItemBackgrounds(fgFavourite2, fgFavourite, fgCustomOpacity);
+ cleanupFramebuffer(itemFramebuffers[1], sw, sh);
+ GL11.glPopMatrix();
+ }
+
+ private static final ResourceLocation RES_ITEM_GLINT = new ResourceLocation("textures/misc/enchanted_item_glint.png");
+
+ float itemRenderOffset = 7.5001f;
+ private void renderItemsFromImage(int xOffset, int width, int height) {
+ if(itemFramebuffers[0] != null && itemFramebuffers[1] != null) {
+ itemFramebuffers[1].bindFramebufferTexture();
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Utils.drawTexturedRect(xOffset-10, 0, width, height, 0, 1, 1, 0);
+ itemFramebuffers[1].unbindFramebufferTexture();
+
+ GL11.glTranslatef(0, 0, itemRenderOffset);
+ itemFramebuffers[0].bindFramebufferTexture();
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Utils.drawTexturedRect(xOffset-10, 0, width, height, 0, 1, 1, 0);
+ itemFramebuffers[0].unbindFramebufferTexture();
+ GL11.glTranslatef(0, 0, -itemRenderOffset);
+ }
+ }
+
+ private void renderEnchOverlay() {
+ ItemStack stack = new ItemStack(Items.apple);
+ IBakedModel model = Minecraft.getMinecraft().getRenderItem().getItemModelMesher()
+ .getItemModel(stack);
+ float f = (float)(Minecraft.getSystemTime() % 3000L) / 3000.0F / 8.0F;
+ float f1 = (float)(Minecraft.getSystemTime() % 4873L) / 4873.0F / 8.0F;
+ Minecraft.getMinecraft().getTextureManager().bindTexture(RES_ITEM_GLINT);
+
+ GL11.glPushMatrix();
+ GL11.glTranslatef(0, 0, -7.5001f+itemRenderOffset);
+ iterateItemSlots(new ItemSlotConsumer() {
+ public void consume(int x, int y, int id) {
+ JsonObject json = getSearchedItemPage(id);
+ if (json == null || !manager.jsonToStack(json).hasEffect()) {
+ return;
+ }
+
+ GlStateManager.pushMatrix();
+ GlStateManager.enableRescaleNormal();
+ GlStateManager.enableAlpha();
+ GlStateManager.alphaFunc(516, 0.1F);
+ GlStateManager.enableBlend();
+
+ GlStateManager.disableLighting();
+
+ GlStateManager.translate(x, y, 0);
+ GlStateManager.scale(16f, 16f, 16f);
+
+ GlStateManager.depthMask(false);
+ GlStateManager.depthFunc(GL11.GL_EQUAL);
+ GlStateManager.blendFunc(GL11.GL_SRC_COLOR, GL11.GL_ONE);
+ GlStateManager.matrixMode(5890);
+ GlStateManager.pushMatrix();
+ GlStateManager.scale(8.0F, 8.0F, 8.0F);
+ GlStateManager.translate(f, 0.0F, 0.0F);
+ GlStateManager.rotate(-50.0F, 0.0F, 0.0F, 1.0F);
+
+ renderModel(model, -8372020, null);
+
+ GlStateManager.popMatrix();
+ GlStateManager.pushMatrix();
+ GlStateManager.scale(8.0F, 8.0F, 8.0F);
+ GlStateManager.translate(-f1, 0.0F, 0.0F);
+ GlStateManager.rotate(10.0F, 0.0F, 0.0F, 1.0F);
+
+ renderModel(model, -8372020, null);
+
+ GlStateManager.popMatrix();
+ GlStateManager.matrixMode(5888);
+ GlStateManager.blendFunc(770, 771);
+ GlStateManager.depthFunc(515);
+ GlStateManager.depthMask(true);
+
+ GlStateManager.popMatrix();
+
+ }
+ });
+ GlStateManager.disableBlend();
+ GlStateManager.disableAlpha();
+ GlStateManager.disableRescaleNormal();
+ GL11.glTranslatef(0, 0, 7.5001f-itemRenderOffset);
+ GL11.glPopMatrix();
+
+ GlStateManager.bindTexture(0);
+ }
+
+ private void renderModel(IBakedModel model, int color, ItemStack stack) {
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+ worldrenderer.begin(7, DefaultVertexFormats.ITEM);
+
+ for (EnumFacing enumfacing : EnumFacing.values()) {
+ this.renderQuads(worldrenderer, model.getFaceQuads(enumfacing), color);
+ }
+
+ this.renderQuads(worldrenderer, model.getGeneralQuads(), color);
+
+ tessellator.draw();
+ }
+
+ private void renderQuads(WorldRenderer renderer, List<BakedQuad> quads, int color) {
+ if(quads == null) return;
+
+ for(BakedQuad quad : quads) {
+ renderer.addVertexData(quad.getVertexData());
+ renderer.putColor4(color);
+ }
+ }
+
+ private void renderItemBackgrounds(Color fgFavourite2, Color fgFavourite, Color fgCustomOpacity) {
+ if(fgCustomOpacity.getAlpha() == 0) return;
+ iterateItemSlots(new ItemSlotConsumer() {
+ public void consume(int x, int y, int id) {
+ JsonObject json = getSearchedItemPage(id);
+ if (json == null) {
+ return;
+ }
+
+ Minecraft.getMinecraft().getTextureManager().bindTexture(item_mask);
+ if (getFavourites().contains(json.get("internalname").getAsString())) {
+ if(manager.config.itemStyle.value) {
+ GlStateManager.color(fgFavourite2.getRed() / 255f, fgFavourite2.getGreen() / 255f,
+ fgFavourite2.getBlue() / 255f, fgFavourite2.getAlpha() / 255f);
+ Utils.drawTexturedRect(x - 1, y - 1, ITEM_SIZE + 2, ITEM_SIZE + 2, GL11.GL_NEAREST);
+
+ GlStateManager.color(fgFavourite.getRed() / 255f, fgFavourite.getGreen() / 255f,
+ fgFavourite.getBlue() / 255f, fgFavourite.getAlpha() / 255f);
+ Utils.drawTexturedRect(x, y, ITEM_SIZE, ITEM_SIZE, GL11.GL_NEAREST);
+ } else {
+ drawRect(x-1, y-1, x+ITEM_SIZE+1, y+ITEM_SIZE+1, fgFavourite2.getRGB());
+ drawRect(x, y, x+ITEM_SIZE, y+ITEM_SIZE, fgFavourite.getRGB());
+ }
+ } else {
+ if(manager.config.itemStyle.value) {
+ GlStateManager.color(fgCustomOpacity.getRed() / 255f, fgCustomOpacity.getGreen() / 255f,
+ fgCustomOpacity.getBlue() / 255f, fgCustomOpacity.getAlpha() / 255f);
+ Utils.drawTexturedRect(x - 1, y - 1, ITEM_SIZE + 2, ITEM_SIZE + 2, GL11.GL_NEAREST);
+ } else {
+ drawRect(x-1, y-1, x+ITEM_SIZE+1, y+ITEM_SIZE+1, fgCustomOpacity.getRGB());
+ }
+ }
+ GlStateManager.bindTexture(0);
+ }
+ }, 10);
+ }
+
+ private void renderItems(int xStart, boolean items, boolean entities) {
+ iterateItemSlots(new ItemSlotConsumer() {
+ public void consume(int x, int y, int id) {
+ JsonObject json = getSearchedItemPage(id);
+ if (json == null) {
+ return;
+ }
+
+ if (json.has("entityrender")) {
+ if(!entities) return;
+ String name = json.get("displayname").getAsString();
+ String[] split = name.split(" \\(");
+ name = name.substring(0, name.length() - split[split.length - 1].length() - 2);
+
+ Class<? extends EntityLivingBase>[] entities = new Class[1];
+ if (json.get("entityrender").isJsonArray()) {
+ JsonArray entityrender = json.get("entityrender").getAsJsonArray();
+ entities = new Class[entityrender.size()];
+ for (int i = 0; i < entityrender.size(); i++) {
+ Class<? extends Entity> clazz = EntityList.stringToClassMapping.get(entityrender.get(i).getAsString());
+ if (clazz != null && EntityLivingBase.class.isAssignableFrom(clazz)) {
+ entities[i] = (Class<? extends EntityLivingBase>) clazz;
+ }
+ }
+ } else if (json.get("entityrender").isJsonPrimitive()) {
+ Class<? extends Entity> clazz = EntityList.stringToClassMapping.get(json.get("entityrender").getAsString());
+ if (clazz != null && EntityLivingBase.class.isAssignableFrom(clazz)) {
+ entities[0] = (Class<? extends EntityLivingBase>) clazz;
+ }
+ }
+
+ float scale = 8;
+ if (json.has("entityscale")) {
+ scale *= json.get("entityscale").getAsFloat();
+ }
+
+ renderEntity(x + ITEM_SIZE / 2, y + ITEM_SIZE, scale, name, entities);
+ } else {
+ if(!items) return;
+ ItemStack stack = manager.jsonToStack(json);
+ Utils.drawItemStackWithoutGlint(stack, x, y);
+ }
+ }
+ }, xStart);
+ }
+
+ public float getItemPaneOffsetFactor() {
+ return itemPaneOffsetFactor.getValue() * getWidthMult() + (1-getWidthMult());
+ }
+
+ public float getInfoPaneOffsetFactor() {
+ return infoPaneOffsetFactor.getValue() * getWidthMult();
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
new file mode 100644
index 00000000..28b35a0b
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
@@ -0,0 +1,584 @@
+package io.github.moulberry.notenoughupdates;
+
+import com.google.common.collect.Sets;
+import com.google.gson.JsonObject;
+import com.mojang.authlib.Agent;
+import com.mojang.authlib.exceptions.AuthenticationException;
+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
+import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.gui.inventory.GuiChest;
+import net.minecraft.client.gui.inventory.GuiContainer;
+import net.minecraft.inventory.ContainerChest;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.scoreboard.ScoreObjective;
+import net.minecraft.scoreboard.Scoreboard;
+import net.minecraft.util.ChatComponentText;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.Session;
+import net.minecraft.util.StatCollector;
+import net.minecraftforge.client.event.ClientChatReceivedEvent;
+import net.minecraftforge.client.event.GuiOpenEvent;
+import net.minecraftforge.client.event.GuiScreenEvent;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.entity.player.ItemTooltipEvent;
+import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.common.Mod.EventHandler;
+import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.GL11;
+
+import javax.swing.*;
+import java.io.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.Proxy;
+import java.text.NumberFormat;
+import java.util.*;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@Mod(modid = NotEnoughUpdates.MODID, version = NotEnoughUpdates.VERSION)
+public class NotEnoughUpdates {
+ public static final String MODID = "notenoughupdates";
+ public static final String VERSION = "1.0.0";
+
+ private NEUManager manager;
+ private NEUOverlay overlay;
+ private NEUIO neuio;
+
+ private static final long CHAT_MSG_COOLDOWN = 200;
+ private long lastChatMessage = 0;
+ private String currChatMessage = null;
+
+ private boolean hoverInv = false;
+ private boolean focusInv = false;
+
+ //Stolen from Biscut and used for detecting whether in skyblock
+ private static final Set<String> SKYBLOCK_IN_ALL_LANGUAGES = Sets.newHashSet("SKYBLOCK","\u7A7A\u5C9B\u751F\u5B58");
+
+ //Github Access Token, may change. Value hard-coded.
+ //Token is obfuscated so that github doesn't delete it whenever I upload the jar.
+ String[] token = new String[]{"b292496d2c","9146a","9f55d0868a545305a8","96344bf"};
+ private String getAccessToken() {
+ String s = "";
+ for(String str : token) {
+ s += str;
+ }
+ return s;
+ }
+
+ @EventHandler
+ public void preinit(FMLPreInitializationEvent event) {
+ MinecraftForge.EVENT_BUS.register(this);
+
+ File f = new File(event.getModConfigurationDirectory(), "notenoughupdates");
+ f.mkdirs();
+ neuio = new NEUIO(getAccessToken());
+ manager = new NEUManager(this, neuio, f);
+ manager.loadItemInformation();
+ overlay = new NEUOverlay(manager);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ try {
+ File tmp = new File(f, "tmp");
+ if(tmp.exists()) {
+ for(File tmpFile : tmp.listFiles()) {
+ tmpFile.delete();
+ }
+ tmp.delete();
+ }
+
+ manager.saveConfig();
+ } catch(IOException e) {}
+ }));
+
+ //TODO: login code. Ignore this, used for testing.
+ try {
+ Field field = Minecraft.class.getDeclaredField("session");
+ YggdrasilUserAuthentication auth = (YggdrasilUserAuthentication)
+ new YggdrasilAuthenticationService(Proxy.NO_PROXY, UUID.randomUUID().toString())
+ .createUserAuthentication(Agent.MINECRAFT);
+ auth.setUsername("james.jenour@protonmail.com");
+ JPasswordField pf = new JPasswordField();
+ JOptionPane.showConfirmDialog(null,
+ pf,
+ "Enter password:",
+ JOptionPane.NO_OPTION,
+ JOptionPane.PLAIN_MESSAGE);
+ auth.setPassword(new String(pf.getPassword()));
+ System.out.print("Attempting login...");
+
+ auth.logIn();
+
+ Session session = new Session(auth.getSelectedProfile().getName(),
+ auth.getSelectedProfile().getId().toString().replace("-", ""),
+ auth.getAuthenticatedToken(),
+ auth.getUserType().getName());
+
+ Field modifiersField = Field.class.getDeclaredField("modifiers");
+ modifiersField.setAccessible(true);
+ modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
+
+ field.setAccessible(true);
+ field.set(Minecraft.getMinecraft(), session);
+ } catch (NoSuchFieldException | AuthenticationException | IllegalAccessException e) {
+ }
+ }
+
+ public void sendChatMessage(String message) {
+ if (System.currentTimeMillis() - lastChatMessage > CHAT_MSG_COOLDOWN) {
+ lastChatMessage = System.currentTimeMillis();
+ Minecraft.getMinecraft().thePlayer.sendChatMessage(message);
+ currChatMessage = null;
+ } else {
+ currChatMessage = message;
+ }
+ }
+
+ @EventHandler
+ public void onTick(TickEvent.ClientTickEvent event) {
+ if(currChatMessage != null && System.currentTimeMillis() - lastChatMessage > CHAT_MSG_COOLDOWN) {
+ lastChatMessage = System.currentTimeMillis();
+ Minecraft.getMinecraft().thePlayer.sendChatMessage(currChatMessage);
+ currChatMessage = null;
+ }
+ }
+
+ AtomicBoolean missingRecipe = new AtomicBoolean(false);
+ @SubscribeEvent
+ public void onGuiOpen(GuiOpenEvent event) {
+ if(event.gui != null) {
+ System.out.println("2");
+ if(event.gui instanceof GuiChest) {
+ GuiChest eventGui = (GuiChest) event.gui;
+ ContainerChest cc = (ContainerChest) eventGui.inventorySlots;
+ IInventory lower = cc.getLowerChestInventory();
+ System.out.println("3");
+ ses.schedule(() -> {
+ if(Minecraft.getMinecraft().currentScreen != event.gui) {
+ return;
+ }
+ if(lower.getStackInSlot(23).getDisplayName().endsWith("Crafting Table")) {
+ try {
+ ItemStack res = lower.getStackInSlot(25);
+ String resInternalname = manager.getInternalNameForItem(res);
+
+ if(lower.getStackInSlot(48) != null) {
+ String backName = null;
+ NBTTagCompound tag = lower.getStackInSlot(48).getTagCompound();
+ if(tag.hasKey("display", 10)) {
+ NBTTagCompound nbttagcompound = tag.getCompoundTag("display");
+ if(nbttagcompound.getTagId("Lore") == 9){
+ NBTTagList nbttaglist1 = nbttagcompound.getTagList("Lore", 8);
+ backName = nbttaglist1.getStringTagAt(0);
+ }
+ }
+
+ if(backName != null) {
+ String[] split = backName.split(" ");
+ if(split[split.length-1].contains("Rewards")) {
+ String col = backName.substring(split[0].length()+1,
+ backName.length()-split[split.length-1].length()-1);
+
+ JsonObject json = manager.getItemInformation().get(resInternalname);
+ json.addProperty("crafttext", "Requires: " + col);
+
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Added: " + resInternalname));
+ manager.writeJsonDefaultDir(json, resInternalname+".json");
+ manager.loadItem(resInternalname);
+ }
+ }
+ }
+
+ /*JsonArray arr = null;
+ File f = new File(manager.configLocation, "missing.json");
+ try(InputStream instream = new FileInputStream(f)) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(instream, StandardCharsets.UTF_8));
+ JsonObject json = manager.gson.fromJson(reader, JsonObject.class);
+ arr = json.getAsJsonArray("missing");
+ } catch(IOException e) {}
+ try {
+ JsonObject json = new JsonObject();
+ JsonArray newArr = new JsonArray();
+ for(JsonElement e : arr) {
+ if(!e.getAsString().equals(resInternalname)) {
+ newArr.add(e);
+ }
+ }
+ json.add("missing", newArr);
+ manager.writeJson(json, f);
+ } catch(IOException e) {}*/
+
+
+
+ /*JsonObject recipe = new JsonObject();
+
+ String[] x = {"1","2","3"};
+ String[] y = {"A","B","C"};
+
+ for(int i=0; i<=18; i+=9) {
+ for(int j=0; j<3; j++) {
+ ItemStack stack = lower.getStackInSlot(10+i+j);
+ String internalname = "";
+ if(stack != null) {
+ internalname = manager.getInternalNameForItem(stack);
+ if(!manager.getItemInformation().containsKey(internalname)) {
+ manager.writeItemToFile(stack);
+ }
+ internalname += ":"+stack.stackSize;
+ }
+ recipe.addProperty(y[i/9]+x[j], internalname);
+ }
+ }
+
+ JsonObject json = manager.getJsonForItem(res);
+ json.add("recipe", recipe);
+ json.addProperty("internalname", resInternalname);
+ json.addProperty("clickcommand", "viewrecipe");
+ json.addProperty("modver", NotEnoughUpdates.VERSION);
+
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Added: " + resInternalname));
+ manager.writeJsonDefaultDir(json, resInternalname+".json");
+ manager.loadItem(resInternalname);*/
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }, 200, TimeUnit.MILLISECONDS);
+ return;
+ }
+ }
+ //OPEN
+ if(Minecraft.getMinecraft().currentScreen == null
+ && event.gui instanceof GuiContainer) {
+ overlay.reset();
+ }
+ //CLOSE
+ if(Minecraft.getMinecraft().currentScreen != null && event.gui == null) {
+ try {
+ manager.saveConfig();
+ } catch(IOException e) {}
+ }
+ }
+
+ @SubscribeEvent
+ public void onGuiChat(ClientChatReceivedEvent e) {
+ String r = null;
+ String unformatted = e.message.getUnformattedText().replaceAll("(?i)\\u00A7.", "");
+ if(unformatted.startsWith("You are playing on profile: ")) {
+ manager.currentProfile = unformatted.substring("You are playing on profile: ".length()).split(" ")[0].trim();
+ } else if(unformatted.startsWith("Your profile was changed to: ")) {//Your profile was changed to:
+ manager.currentProfile = unformatted.substring("Your profile was changed to: ".length()).split(" ")[0].trim();
+ }
+ if(e.message.getFormattedText().equals(EnumChatFormatting.RESET.toString()+
+ EnumChatFormatting.RED+"You haven't unlocked this recipe!"+EnumChatFormatting.RESET)) {
+ r = EnumChatFormatting.RED+"You haven't unlocked this recipe!";
+ } else if(e.message.getFormattedText().startsWith(EnumChatFormatting.RESET.toString()+
+ EnumChatFormatting.RED+"Invalid recipe ")) {
+ r = "";
+ }
+ if(r != null) {
+ if(manager.failViewItem(r)) {
+ e.setCanceled(true);
+ }
+ missingRecipe.set(true);
+ }
+ }
+
+ @SubscribeEvent
+ public void onGuiBackgroundDraw(GuiScreenEvent.BackgroundDrawnEvent event) {
+ if(event.gui instanceof GuiContainer && isOnSkyblock()) {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int width = scaledresolution.getScaledWidth();
+
+ boolean hoverPane = event.getMouseX() < width*overlay.getInfoPaneOffsetFactor() ||
+ event.getMouseX() > width*overlay.getItemPaneOffsetFactor();
+ try {
+ int xSize = (int) Utils.getField(GuiContainer.class, event.gui, "xSize", "field_146999_f");
+ int ySize = (int) Utils.getField(GuiContainer.class, event.gui, "ySize", "field_147000_g");
+ int guiLeft = (int) Utils.getField(GuiContainer.class, event.gui, "guiLeft", "field_147003_i");
+ int guiTop = (int) Utils.getField(GuiContainer.class, event.gui, "guiTop", "field_147009_r");
+
+ hoverInv = event.getMouseX() > guiLeft && event.getMouseX() < guiLeft + xSize &&
+ event.getMouseY() > guiTop && event.getMouseY() < guiTop + ySize;
+
+ if(hoverPane) {
+ if(!hoverInv) focusInv = false;
+ } else {
+ focusInv = true;
+ }
+ } catch(NullPointerException npe) {
+ npe.printStackTrace();
+ focusInv = !hoverPane;
+ }
+
+ if(focusInv) {
+ try {
+ overlay.render(event.getMouseX(), event.getMouseY(), hoverInv && focusInv);
+ } catch(ConcurrentModificationException e) {e.printStackTrace();}
+ GL11.glTranslatef(0, 0, 10);
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void onGuiScreenDraw(GuiScreenEvent.DrawScreenEvent.Post event) {
+ if(event.gui instanceof GuiContainer && isOnSkyblock()) {
+ if(!focusInv) {
+ GL11.glTranslatef(0, 0, 300);
+ overlay.render(event.mouseX, event.mouseY, hoverInv && focusInv);
+ GL11.glTranslatef(0, 0, -300);
+ }
+ overlay.renderOverlay(event.mouseX, event.mouseY);
+ }
+ }
+
+ @SubscribeEvent
+ public void onGuiScreenMouse(GuiScreenEvent.MouseInputEvent.Pre event) {
+ if(event.gui instanceof GuiContainer && !(hoverInv && focusInv) && isOnSkyblock()) {
+ if(overlay.mouseInput()) {
+ event.setCanceled(true);
+ }
+ }
+ }
+
+ ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
+
+ boolean started = false;
+ @SubscribeEvent
+ public void onGuiScreenKeyboard(GuiScreenEvent.KeyboardInputEvent.Pre event) {
+ if(manager.config.enableItemEditing.value && Minecraft.getMinecraft().theWorld != null &&
+ Keyboard.getEventKey() == Keyboard.KEY_O && Keyboard.getEventKeyState()) {
+ GuiScreen gui = Minecraft.getMinecraft().currentScreen;
+ if(gui != null && gui instanceof GuiChest) {
+ GuiChest eventGui = (GuiChest) event.gui;
+ ContainerChest cc = (ContainerChest) eventGui.inventorySlots;
+ IInventory lower = cc.getLowerChestInventory();
+
+ if(lower.getStackInSlot(23) != null &&
+ lower.getStackInSlot(23).getDisplayName().endsWith("Crafting Table")) {
+ ItemStack res = lower.getStackInSlot(25);
+ String resInternalname = manager.getInternalNameForItem(res);
+ JTextField tf = new JTextField();
+ tf.setText(resInternalname);
+ tf.addAncestorListener(new RequestFocusListener());
+ JOptionPane.showOptionDialog(null,
+ tf,
+ "Enter Name:",
+ JOptionPane.NO_OPTION,
+ JOptionPane.PLAIN_MESSAGE,
+ null, new String[]{"Enter"}, "Enter");
+ resInternalname = tf.getText();
+
+ JsonObject recipe = new JsonObject();
+
+ String[] x = {"1","2","3"};
+ String[] y = {"A","B","C"};
+
+ for(int i=0; i<=18; i+=9) {
+ for(int j=0; j<3; j++) {
+ ItemStack stack = lower.getStackInSlot(10+i+j);
+ String internalname = "";
+ if(stack != null) {
+ internalname = manager.getInternalNameForItem(stack);
+ if(!manager.getItemInformation().containsKey(internalname)) {
+ manager.writeItemToFile(stack);
+ }
+ internalname += ":"+stack.stackSize;
+ }
+ recipe.addProperty(y[i/9]+x[j], internalname);
+ }
+ }
+
+ JsonObject json = manager.getJsonForItem(res);
+ json.add("recipe", recipe);
+ json.addProperty("internalname", resInternalname);
+ json.addProperty("clickcommand", "viewrecipe");
+ json.addProperty("modver", NotEnoughUpdates.VERSION);
+ try {
+ Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Added: " + resInternalname));
+ manager.writeJsonDefaultDir(json, resInternalname+".json");
+ manager.loadItem(resInternalname);
+ } catch(IOException e) {}
+ }
+ }
+ }
+ /*if(Minecraft.getMinecraft().theWorld != null && Keyboard.getEventKey() == Keyboard.KEY_RBRACKET && Keyboard.getEventKeyState()) {
+ Minecraft.getMinecraft().displayGuiScreen(null);
+ started = true;
+ final Object[] items = manager.getItemInformation().values().toArray();
+ AtomicInteger i = new AtomicInteger(0);
+
+ Runnable checker = new Runnable() {
+ @Override
+ public void run() {
+ int in = i.getAndIncrement();
+ /*if(missingRecipe.get()) {
+ String internalname = ((JsonObject)items[in]).get("internalname").getAsString();
+
+ JsonArray arr = null;
+ File f = new File(manager.configLocation, "missing.json");
+ try(InputStream instream = new FileInputStream(f)) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(instream, StandardCharsets.UTF_8));
+ JsonObject json = manager.gson.fromJson(reader, JsonObject.class);
+ arr = json.getAsJsonArray("missing");
+ } catch(IOException e) {}
+
+ try {
+ JsonObject json = new JsonObject();
+ if(arr == null) arr = new JsonArray();
+ arr.add(new JsonPrimitive(internalname));
+ json.add("missing", arr);
+ manager.writeJson(json, f);
+ } catch(IOException e) {}
+ }
+ missingRecipe.set(false);
+
+ ses.schedule(() -> {
+ int index = i.get();
+ JsonObject o = (JsonObject)items[index];
+ if(Minecraft.getMinecraft().currentScreen != null) {
+ Minecraft.getMinecraft().displayGuiScreen(null);
+ }
+ Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewrecipe " + o.get("internalname").getAsString());
+
+ ses.schedule(this, 1000, TimeUnit.MILLISECONDS);
+ }, 100, TimeUnit.MILLISECONDS);
+ }
+ };
+
+ int index = i.get();
+ JsonObject o = (JsonObject)items[index];
+ if(Minecraft.getMinecraft().currentScreen != null) {
+ Minecraft.getMinecraft().displayGuiScreen(null);
+ }
+ Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewrecipe " + o.get("internalname").getAsString());
+
+ ses.schedule(checker, 1000, TimeUnit.MILLISECONDS);
+ }*/
+ if(event.gui instanceof GuiContainer && isOnSkyblock()) {
+ if(overlay.keyboardInput(focusInv)) {
+ event.setCanceled(true);
+ }
+ }
+ }
+
+ /**
+ * This was code leftover from testing but it ended up in the final mod so I guess its staying here.
+ * This makes it so that holding LCONTROL while hovering over an item with NBT will show the NBT of the item.
+ * @param event
+ */
+ @SubscribeEvent
+ public void onItemTooltip(ItemTooltipEvent event) {
+ if(Minecraft.getMinecraft().currentScreen != null) {
+ if(Minecraft.getMinecraft().currentScreen instanceof GuiChest) {
+ GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
+ ContainerChest container = (ContainerChest) chest.inventorySlots;
+ String containerName = container.getLowerChestInventory().getDisplayName().getUnformattedText();
+ if(containerName.trim().equals("Auctions Browser")) {
+ String internalname = manager.getInternalNameForItem(event.itemStack);
+ if(internalname != null) {
+ for(int i=0; i<event.toolTip.size(); i++) {
+ String line = event.toolTip.get(i);
+ if(line.contains(EnumChatFormatting.GRAY + "Bidder: ") ||
+ line.contains(EnumChatFormatting.GRAY + "Starting bid: ") ||
+ line.contains(EnumChatFormatting.GRAY + "Buy it now: ")) {
+ manager.updatePrices();
+ JsonObject auctionInfo = manager.getItemAuctionInfo(internalname);
+
+ if(auctionInfo != null) {
+ NumberFormat format = NumberFormat.getInstance(Locale.US);
+ int auctionPrice = (int)(auctionInfo.get("price").getAsFloat() / auctionInfo.get("count").getAsFloat());
+ float costOfEnchants = manager.getCostOfEnchants(internalname,
+ event.itemStack.getTagCompound());
+ int priceWithEnchants = auctionPrice+(int)costOfEnchants;
+
+ event.toolTip.add(++i, EnumChatFormatting.GRAY + "Average price: " +
+ EnumChatFormatting.GOLD + format.format(auctionPrice) + " coins");
+ if(costOfEnchants > 0) {
+ event.toolTip.add(++i, EnumChatFormatting.GRAY + "Average price (w/ enchants): " +
+ EnumChatFormatting.GOLD +
+ format.format(priceWithEnchants) + " coins");
+ }
+
+ if(manager.config.advancedPriceInfo.value) {
+ int salesVolume = (int) auctionInfo.get("sales").getAsFloat();
+ int flipPrice = (int)(0.93*priceWithEnchants);
+
+ event.toolTip.add(++i, EnumChatFormatting.GRAY + "Flip Price (93%): " +
+ EnumChatFormatting.GOLD + format.format(flipPrice) + " coins");
+ event.toolTip.add(++i, EnumChatFormatting.GRAY + "Volume: " +
+ EnumChatFormatting.GOLD + format.format(salesVolume) + " sales/day");
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(!Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || !manager.config.dev.value) return;
+ if(event.toolTip.get(event.toolTip.size()-1).startsWith(EnumChatFormatting.DARK_GRAY + "NBT: ")) {
+ event.toolTip.remove(event.toolTip.size()-1);
+
+ StringBuilder sb = new StringBuilder();
+ String nbt = event.itemStack.getTagCompound().toString();
+ int indent = 0;
+ for(char c : nbt.toCharArray()) {
+ boolean newline = false;
+ if(c == '{' || c == '[') {
+ indent++;
+ newline = true;
+ } else if(c == '}' || c == ']') {
+ indent--;
+ sb.append("\n");
+ for(int i=0; i<indent; i++) sb.append(" ");
+ } else if(c == ',') {
+ newline = true;
+ } else if(c == '\"') {
+ sb.append(EnumChatFormatting.RESET.toString() + EnumChatFormatting.GRAY);
+ }
+
+ sb.append(c);
+ if(newline) {
+ sb.append("\n");
+ for(int i=0; i<indent; i++) sb.append(" ");
+ }
+ }
+ event.toolTip.add(sb.toString());
+ }
+ }
+
+ //Stolen from Biscut's SkyblockAddons
+ public boolean isOnSkyblock() {
+ if(!manager.config.onlyShowOnSkyblock.value) return true;
+
+ Minecraft mc = Minecraft.getMinecraft();
+
+ if (mc != null && mc.theWorld != null) {
+ Scoreboard scoreboard = mc.theWorld.getScoreboard();
+ ScoreObjective sidebarObjective = scoreboard.getObjectiveInDisplaySlot(1);
+ if (sidebarObjective != null) {
+ String objectiveName = sidebarObjective.getDisplayName().replaceAll("(?i)\\u00A7.", "");
+ for (String skyblock : SKYBLOCK_IN_ALL_LANGUAGES) {
+ if (objectiveName.startsWith(skyblock)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/RequestFocusListener.java b/src/main/java/io/github/moulberry/notenoughupdates/RequestFocusListener.java
new file mode 100644
index 00000000..ecc27c9a
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/RequestFocusListener.java
@@ -0,0 +1,63 @@
+package io.github.moulberry.notenoughupdates;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+/**
+ * Convenience class to request focus on a component.
+ *
+ * When the component is added to a realized Window then component will
+ * request focus immediately, since the ancestorAdded event is fired
+ * immediately.
+ *
+ * When the component is added to a non realized Window, then the focus
+ * request will be made once the window is realized, since the
+ * ancestorAdded event will not be fired until then.
+ *
+ * Using the default constructor will cause the listener to be removed
+ * from the component once the AncestorEvent is generated. A second constructor
+ * allows you to specify a boolean value of false to prevent the
+ * AncestorListener from being removed when the event is generated. This will
+ * allow you to reuse the listener each time the event is generated.
+ */
+public class RequestFocusListener implements AncestorListener
+{
+ private boolean removeListener;
+
+ /*
+ * Convenience constructor. The listener is only used once and then it is
+ * removed from the component.
+ */
+ public RequestFocusListener()
+ {
+ this(true);
+ }
+
+ /*
+ * Constructor that controls whether this listen can be used once or
+ * multiple times.
+ *
+ * @param removeListener when true this listener is only invoked once
+ * otherwise it can be invoked multiple times.
+ */
+ public RequestFocusListener(boolean removeListener)
+ {
+ this.removeListener = removeListener;
+ }
+
+ @Override
+ public void ancestorAdded(AncestorEvent e)
+ {
+ JComponent component = e.getComponent();
+ component.requestFocusInWindow();
+
+ if (removeListener)
+ component.removeAncestorListener( this );
+ }
+
+ @Override
+ public void ancestorMoved(AncestorEvent e) {}
+
+ @Override
+ public void ancestorRemoved(AncestorEvent e) {}
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/Utils.java b/src/main/java/io/github/moulberry/notenoughupdates/Utils.java
new file mode 100644
index 00000000..c08adc94
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/Utils.java
@@ -0,0 +1,431 @@
+package io.github.moulberry.notenoughupdates;
+
+import com.mojang.authlib.Agent;
+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
+import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
+import io.github.moulberry.notenoughupdates.util.TexLoc;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.inventory.GuiContainer;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.RenderHelper;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.Session;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL14;
+
+import javax.swing.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Utils {
+
+ private static boolean hasEffectOverride = false;
+
+ public static <T> ArrayList<T> createList(T... values) {
+ ArrayList<T> list = new ArrayList<>();
+ for(T value : values)list.add(value);
+ return list;
+ }
+
+ public static boolean getHasEffectOverride() {
+ return hasEffectOverride;
+ }
+
+ public static void drawItemStackWithoutGlint(ItemStack stack, int x, int y) {
+ RenderItem itemRender = Minecraft.getMinecraft().getRenderItem();
+
+ RenderHelper.enableGUIStandardItemLighting();
+ itemRender.zLevel = -145; //Negates the z-offset of the below method.
+ hasEffectOverride = true;
+ try {
+ itemRender.renderItemAndEffectIntoGUI(stack, x, y);
+ } catch(Exception e) {e.printStackTrace();} //Catch exceptions to ensure that hasEffectOverride is set back to false.
+ hasEffectOverride = false;
+ itemRender.zLevel = 0;
+ RenderHelper.disableStandardItemLighting();
+ }
+
+ public static void drawItemStack(ItemStack stack, int x, int y) {
+ RenderItem itemRender = Minecraft.getMinecraft().getRenderItem();
+
+ RenderHelper.enableGUIStandardItemLighting();
+ itemRender.zLevel = -145; //Negates the z-offset of the below method.
+ itemRender.renderItemAndEffectIntoGUI(stack, x, y);
+ itemRender.zLevel = 0;
+ RenderHelper.disableStandardItemLighting();
+ }
+
+ public static Method getMethod(Class<?> clazz, Class<?>[] params, String... methodNames) {
+ for(String methodName : methodNames) {
+ try {
+ return clazz.getDeclaredMethod(methodName, params);
+ } catch(Exception e) {}
+ }
+ return null;
+ }
+
+ public static Object getField(Class<?> clazz, Object o, String... fieldNames) {
+ Field field = null;
+ for(String fieldName : fieldNames) {
+ try {
+ field = clazz.getDeclaredField(fieldName);
+ break;
+ } catch(Exception e) {}
+ }
+ if(field != null) {
+ field.setAccessible(true);
+ try {
+ return field.get(o);
+ } catch(IllegalAccessException e) {
+ }
+ }
+ return null;
+ }
+
+ public static Slot getSlotUnderMouse(GuiContainer container) {
+ return (Slot) getField(GuiContainer.class, container, "theSlot", "field_147006_u");
+ }
+
+ public static void drawTexturedRect(float x, float y, float width, float height) {
+ drawTexturedRect(x, y, width, height, 0, 1, 0 , 1);
+ }
+
+ public static void drawTexturedRect(float x, float y, float width, float height, int filter) {
+ drawTexturedRect(x, y, width, height, 0, 1, 0 , 1, filter);
+ }
+
+ public static void drawTexturedRect(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax) {
+ drawTexturedRect(x, y, width, height, uMin, uMax, vMin , vMax, GL11.GL_LINEAR);
+ }
+
+ public static void drawTexturedRect(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax, int filter) {
+ GlStateManager.enableTexture2D();
+ GlStateManager.enableBlend();
+ GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
+
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter);
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter);
+
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+ worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
+ worldrenderer
+ .pos(x, y+height, 0.0D)
+ .tex(uMin, vMax).endVertex();
+ worldrenderer
+ .pos(x+width, y+height, 0.0D)
+ .tex(uMax, vMax).endVertex();
+ worldrenderer
+ .pos(x+width, y, 0.0D)
+ .tex(uMax, vMin).endVertex();
+ worldrenderer
+ .pos(x, y, 0.0D)
+ .tex(uMin, vMin).endVertex();
+ tessellator.draw();
+
+ /*Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+ worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX);
+ worldrenderer
+ .pos(x, y + height, 0)
+ .tex(uMin, vMax).endVertex();
+ worldrenderer
+ .pos(x + width, y + height, 0)
+ .tex(uMax, vMax).endVertex();
+ worldrenderer
+ .pos(x + width, y, 0)
+ .tex(uMax, vMin).endVertex();
+ worldrenderer
+ .pos(x, y, 0)
+ .tex(uMin, vMin).endVertex();
+ tessellator.draw();*/
+
+ /*GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
+ GL11.glTexCoord2f(uMin, vMin);
+ GL11.glVertex3f(x, y, 0.0F);
+ GL11.glTexCoord2f(uMin, vMax);
+ GL11.glVertex3f(x, y+height, 0.0F);
+ GL11.glTexCoord2f(uMax, vMin);
+ GL11.glVertex3f(x+width, y, 0.0F);
+ GL11.glTexCoord2f(uMax, vMax);
+ GL11.glVertex3f(x+width, y+height, 0.0F);
+ GL11.glEnd();*/
+
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
+ GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
+
+ GlStateManager.disableBlend();
+ }
+
+ public static void drawStringScaledMaxWidth(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) {
+ int strLen = fr.getStringWidth(str);
+ float factor = len/(float)strLen;
+ factor = Math.min(1, factor);
+
+ drawStringScaled(str, fr, x, y, shadow, colour, factor);
+ }
+
+ public static void drawStringScaled(String str, FontRenderer fr, float x, float y, boolean shadow, int colour, float factor) {
+ GlStateManager.scale(factor, factor, 1);
+ fr.drawString(str, x/factor, y/factor, colour, shadow);
+ GlStateManager.scale(1/factor, 1/factor, 1);
+ }
+
+ public static void drawStringCenteredScaledMaxWidth(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) {
+ int strLen = fr.getStringWidth(str);
+ float factor = len/(float)strLen;
+ factor = Math.min(1, factor);
+ int newLen = Math.min(strLen, len);
+
+ float fontHeight = 8*factor;
+
+ drawStringScaled(str, fr, x-newLen/2, y-fontHeight/2, shadow, colour, factor);
+ }
+
+ public static void drawStringCenteredScaled(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) {
+ int strLen = fr.getStringWidth(str);
+ float factor = len/(float)strLen;
+ float fontHeight = 8*factor;
+
+ drawStringScaled(str, fr, x-len/2, y-fontHeight/2, shadow, colour, factor);
+ }
+
+ public static int renderStringTrimWidth(String str, FontRenderer fr, boolean shadow, int x, int y, int len, int colour, int maxLines) {
+ int yOff = 0;
+ String excess;
+ String trimmed = trimToWidth(str, len);
+
+ String colourCodes = "";
+ Pattern pattern = Pattern.compile("\\u00A7.");
+ Matcher matcher = pattern.matcher(trimmed);
+ while(matcher.find()) {
+ colourCodes += matcher.group();
+ }
+
+ boolean firstLine = true;
+ int trimmedCharacters = trimmed.length();
+ int lines = 0;
+ while((lines++<maxLines) || maxLines<0) {
+ if(trimmed.length() == str.length()) {
+ fr.drawString(trimmed, x, y + yOff, colour, shadow);
+ break;
+ } else if(trimmed.isEmpty()) {
+ yOff -= 12;
+ break;
+ } else {
+ if(firstLine) {
+ fr.drawString(trimmed, x, y + yOff, colour, shadow);
+ firstLine = false;
+ } else {
+ if(trimmed.startsWith(" ")) {
+ trimmed = trimmed.substring(1);
+ }
+ fr.drawString(colourCodes + trimmed, x, y + yOff, colour, shadow);
+ }
+
+ excess = str.substring(trimmedCharacters);
+ trimmed = trimToWidth(excess, len);
+ trimmedCharacters += trimmed.length();
+ yOff += 12;
+ }
+ }
+ return yOff;
+ }
+
+ public static String trimToWidth(String str, int len) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+ String trim = fr.trimStringToWidth(str, len);
+
+ if(str.length() != trim.length() && !trim.endsWith(" ")) {
+ char next = str.charAt(trim.length());
+ if(next != ' ') {
+ String[] split = trim.split(" ");
+ String last = split[split.length-1];
+ if(last.length() < 8) {
+ trim = trim.substring(0, trim.length()-last.length());
+ }
+ }
+ }
+
+ return trim;
+ }
+
+ public static void drawHoveringText(List<String> textLines, final int mouseX, final int mouseY, final int screenWidth, final int screenHeight, final int maxTextWidth, FontRenderer font) {
+ if (!textLines.isEmpty())
+ {
+ GlStateManager.disableRescaleNormal();
+ RenderHelper.disableStandardItemLighting();
+ GlStateManager.disableLighting();
+ GlStateManager.disableDepth();
+ int tooltipTextWidth = 0;
+
+ for (String textLine : textLines)
+ {
+ int textLineWidth = font.getStringWidth(textLine);
+
+ if (textLineWidth > tooltipTextWidth)
+ {
+ tooltipTextWidth = textLineWidth;
+ }
+ }
+
+ boolean needsWrap = false;
+
+ int titleLinesCount = 1;
+ int tooltipX = mouseX + 12;
+ if (tooltipX + tooltipTextWidth + 4 > screenWidth)
+ {
+ tooltipX = mouseX - 16 - tooltipTextWidth;
+ if (tooltipX < 4) // if the tooltip doesn't fit on the screen
+ {
+ if (mouseX > screenWidth / 2)
+ {
+ tooltipTextWidth = mouseX - 12 - 8;
+ }
+ else
+ {
+ tooltipTextWidth = screenWidth - 16 - mouseX;
+ }
+ needsWrap = true;
+ }
+ }
+
+ if (maxTextWidth > 0 && tooltipTextWidth > maxTextWidth)
+ {
+ tooltipTextWidth = maxTextWidth;
+ needsWrap = true;
+ }
+
+ if (needsWrap)
+ {
+ int wrappedTooltipWidth = 0;
+ List<String> wrappedTextLines = new ArrayList<String>();
+ for (int i = 0; i < textLines.size(); i++)
+ {
+ String textLine = textLines.get(i);
+ List<String> wrappedLine = font.listFormattedStringToWidth(textLine, tooltipTextWidth);
+ if (i == 0)
+ {
+ titleLinesCount = wrappedLine.size();
+ }
+
+ for (String line : wrappedLine)
+ {
+ int lineWidth = font.getStringWidth(line);
+ if (lineWidth > wrappedTooltipWidth)
+ {
+ wrappedTooltipWidth = lineWidth;
+ }
+ wrappedTextLines.add(line);
+ }
+ }
+ tooltipTextWidth = wrappedTooltipWidth;
+ textLines = wrappedTextLines;
+
+ if (mouseX > screenWidth / 2)
+ {
+ tooltipX = mouseX - 16 - tooltipTextWidth;
+ }
+ else
+ {
+ tooltipX = mouseX + 12;
+ }
+ }
+
+ int tooltipY = mouseY - 12;
+ int tooltipHeight = 8;
+
+ if (textLines.size() > 1)
+ {
+ tooltipHeight += (textLines.size() - 1) * 10;
+ if (textLines.size() > titleLinesCount) {
+ tooltipHeight += 2; // gap between title lines and next lines
+ }
+ }
+
+ if (tooltipY + tooltipHeight + 6 > screenHeight)
+ {
+ tooltipY = screenHeight - tooltipHeight - 6;
+ }
+
+ final int zLevel = 300;
+ final int backgroundColor = 0xF0100010;
+ drawGradientRect(zLevel, tooltipX - 3, tooltipY - 4, tooltipX + tooltipTextWidth + 3, tooltipY - 3, backgroundColor, backgroundColor);
+ drawGradientRect(zLevel, tooltipX - 3, tooltipY + tooltipHeight + 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 4, backgroundColor, backgroundColor);
+ drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor);
+ drawGradientRect(zLevel, tooltipX - 4, tooltipY - 3, tooltipX - 3, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor);
+ drawGradientRect(zLevel, tooltipX + tooltipTextWidth + 3, tooltipY - 3, tooltipX + tooltipTextWidth + 4, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor);
+ final int borderColorStart = 0x505000FF;
+ final int borderColorEnd = (borderColorStart & 0xFEFEFE) >> 1 | borderColorStart & 0xFF000000;
+ drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3 + 1, tooltipX - 3 + 1, tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd);
+ drawGradientRect(zLevel, tooltipX + tooltipTextWidth + 2, tooltipY - 3 + 1, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd);
+ drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY - 3 + 1, borderColorStart, borderColorStart);
+ drawGradientRect(zLevel, tooltipX - 3, tooltipY + tooltipHeight + 2, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, borderColorEnd, borderColorEnd);
+
+ for (int lineNumber = 0; lineNumber < textLines.size(); ++lineNumber)
+ {
+ String line = textLines.get(lineNumber);
+ font.drawStringWithShadow(line, (float)tooltipX, (float)tooltipY, -1);
+
+ if (lineNumber + 1 == titleLinesCount)
+ {
+ tooltipY += 2;
+ }
+
+ tooltipY += 10;
+ }
+
+ GlStateManager.enableLighting();
+ GlStateManager.enableDepth();
+ RenderHelper.enableStandardItemLighting();
+ GlStateManager.enableRescaleNormal();
+ }
+ GlStateManager.disableLighting();
+ }
+
+ public static void drawGradientRect(int zLevel, int left, int top, int right, int bottom, int startColor, int endColor) {
+ float startAlpha = (float)(startColor >> 24 & 255) / 255.0F;
+ float startRed = (float)(startColor >> 16 & 255) / 255.0F;
+ float startGreen = (float)(startColor >> 8 & 255) / 255.0F;
+ float startBlue = (float)(startColor & 255) / 255.0F;
+ float endAlpha = (float)(endColor >> 24 & 255) / 255.0F;
+ float endRed = (float)(endColor >> 16 & 255) / 255.0F;
+ float endGreen = (float)(endColor >> 8 & 255) / 255.0F;
+ float endBlue = (float)(endColor & 255) / 255.0F;
+
+ GlStateManager.disableTexture2D();
+ GlStateManager.enableBlend();
+ GlStateManager.disableAlpha();
+ GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
+ GlStateManager.shadeModel(7425);
+
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+ worldrenderer.begin(7, DefaultVertexFormats.POSITION_COLOR);
+ worldrenderer.pos(right, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex();
+ worldrenderer.pos(left, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex();
+ worldrenderer.pos(left, bottom, zLevel).color(endRed, endGreen, endBlue, endAlpha).endVertex();
+ worldrenderer.pos(right, bottom, zLevel).color(endRed, endGreen, endBlue, endAlpha).endVertex();
+ tessellator.draw();
+
+ GlStateManager.shadeModel(7424);
+ GlStateManager.disableBlend();
+ GlStateManager.enableAlpha();
+ GlStateManager.enableTexture2D();
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/DevInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/DevInfoPane.java
new file mode 100644
index 00000000..0abca240
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/DevInfoPane.java
@@ -0,0 +1,106 @@
+package io.github.moulberry.notenoughupdates.infopanes;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import io.github.moulberry.notenoughupdates.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.item.Item;
+import net.minecraft.util.ResourceLocation;
+import org.lwjgl.input.Keyboard;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class DevInfoPane extends TextInfoPane {
+
+ public DevInfoPane(NEUOverlay overlay, NEUManager manager) {
+ super(overlay, manager, "Dev", "");
+ text = getText();
+ }
+
+ private String getText() {
+ String text = "";
+ for(Map.Entry<String, JsonElement> entry : manager.getAuctionPricesJson().get("prices").getAsJsonObject().entrySet()) {
+ if(!manager.getItemInformation().keySet().contains(entry.getKey())) {
+ if(entry.getKey().contains("-")) {
+ continue;
+ }
+ if(entry.getKey().startsWith("PERFECT")) continue;
+ if(Item.itemRegistry.getObject(new ResourceLocation(entry.getKey().toLowerCase())) != null) {
+ continue;
+ }
+ text += entry.getKey() + "\n";
+ }
+ }
+ return text;
+ }
+
+ AtomicBoolean running = new AtomicBoolean(false);
+ ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
+
+ @Override
+ public void keyboardInput() {
+ if(Keyboard.isKeyDown(Keyboard.KEY_J)) {
+ running.set(!running.get());
+
+ if(running.get()) {
+ List<String> add = new ArrayList<>();
+ for(Map.Entry<String, JsonObject> item : manager.getItemInformation().entrySet()) {
+ if(item.getValue().has("recipe")) {
+ if(!item.getKey().contains("-") && !item.getKey().contains(";")) {
+ add.add(item.getKey());
+ }
+ }
+ }
+ AtomicInteger index = new AtomicInteger(0);
+
+ ses.schedule(new Runnable() {
+ public void run() {
+ if(!running.get()) return;
+
+ int i = index.getAndIncrement();
+ String item = add.get(i).split("-")[0].split(";")[0];
+ Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewrecipe " + item);
+ ses.schedule(this, 1000L, TimeUnit.MILLISECONDS);
+ }
+ }, 1000L, TimeUnit.MILLISECONDS);
+ }
+ }
+ /*if(Keyboard.isKeyDown(Keyboard.KEY_J) && !running) {
+ running = true;
+ List<String> add = new ArrayList<>();
+ for(Map.Entry<String, JsonElement> entry : manager.getAuctionPricesJson().get("prices").getAsJsonObject().entrySet()) {
+ if(!manager.getItemInformation().keySet().contains(entry.getKey())) {
+ if(entry.getKey().contains("-")) {
+ continue;
+ }
+ if(entry.getKey().startsWith("PERFECT")) continue;
+ if(Item.itemRegistry.getObject(new ResourceLocation(entry.getKey().toLowerCase())) != null) {
+ continue;
+ }
+ add.add(entry.getKey());
+ }
+ }
+ AtomicInteger index = new AtomicInteger(0);
+
+ ses.schedule(new Runnable() {
+ public void run() {
+ int i = index.getAndIncrement();
+ String item = add.get(i).split("-")[0].split(";")[0];
+ Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewrecipe " + item);
+ ses.schedule(this, 1000L, TimeUnit.MILLISECONDS);
+ }
+ }, 1000L, TimeUnit.MILLISECONDS);
+ }*/
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/FlipperInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/FlipperInfoPane.java
new file mode 100644
index 00000000..49cc5389
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/FlipperInfoPane.java
@@ -0,0 +1,111 @@
+package io.github.moulberry.notenoughupdates.infopanes;
+
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import io.github.moulberry.notenoughupdates.Utils;
+import io.github.moulberry.notenoughupdates.itemeditor.GuiElementButton;
+import io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.util.EnumChatFormatting;
+import org.lwjgl.opengl.GL11;
+
+import static io.github.moulberry.notenoughupdates.GuiTextures.*;
+import static io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField.*;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FlipperInfoPane extends InfoPane {
+
+ protected String title;
+ protected String text;
+
+ GuiElementTextField minPrice = new GuiElementTextField("0", NUM_ONLY | NO_SPACE);
+ GuiElementTextField maxPrice = new GuiElementTextField("100000000", NUM_ONLY | NO_SPACE);
+ GuiElementTextField priceDiff = new GuiElementTextField("1000000", NUM_ONLY | NO_SPACE);
+
+ public FlipperInfoPane(NEUOverlay overlay, NEUManager manager, String title, String text) {
+ super(overlay, manager);
+ this.title = title;
+ this.text = text;
+
+ minPrice.setSize(60, 16);
+ maxPrice.setSize(60, 16);
+ priceDiff.setSize(60, 16);
+ }
+
+ public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, int mouseY) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+
+ int titleLen = fr.getStringWidth(title);
+ fr.drawString(title, (leftSide+rightSide-titleLen)/2, overlay.BOX_PADDING + 5,
+ Color.WHITE.getRGB());
+
+ int y = 0;
+ y += renderParagraph(width, height, y, "Bazaar Flips", bg);
+ //draw controls
+ y += 20;
+ y += renderParagraph(width, height, y, "-- Strong Dragon Fragments", bg);
+ y += renderParagraph(width, height, y, "-- Strong Dragon Fragments", bg);
+ y += renderParagraph(width, height, y, "-- Strong Dragon Fragments", bg);
+ y += renderParagraph(width, height, y, "-- Strong Dragon Fragments", bg);
+
+ y += renderParagraph(width, height, y, "AH Flips", bg);
+ //min price, max price, price diff, blacklist stackables
+ //GuiElementButton stackables = new GuiElementButton("1000000", NUM_ONLY | NO_SPACE);
+
+ y += 10;
+ int x = 10;
+ fr.drawString("Min Price: ", x, y, Color.WHITE.getRGB());
+ minPrice.render(x, y+10); x += 70;
+ fr.drawString("Max Price: ", x, y, Color.WHITE.getRGB());
+ maxPrice.render(x, y+10); x += 70;
+ fr.drawString("Price Diff: ", x, y, Color.WHITE.getRGB());
+ priceDiff.render(x, y+10); x += 70;
+ fr.drawString("Incl. Stackables: ", x, y, Color.WHITE.getRGB());
+ drawButton(x, y, false);
+
+ drawRect(leftSide+overlay.BOX_PADDING-5, overlay.BOX_PADDING-5,
+ rightSide-overlay.BOX_PADDING+5, height-overlay.BOX_PADDING+5, bg.getRGB());
+ }
+
+ private void drawButton(int x, int y, boolean enabled) {
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Minecraft.getMinecraft().getTextureManager().bindTexture((enabled) ? on : off);
+ Utils.drawTexturedRect(x, y, 48, 16);
+ }
+
+ public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) {
+
+ }
+
+ public void keyboardInput() {
+
+ }
+
+ private int renderParagraph(int width, int height, int startY, String text, Color bg) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+
+ int yOff = 0;
+ for(String line : text.split("\n")) {
+ yOff += Utils.renderStringTrimWidth(line, fr, false,leftSide+overlay.BOX_PADDING + 5,
+ startY+overlay.BOX_PADDING + 10 + yOff,
+ width*1/3-overlay.BOX_PADDING*2-10, Color.WHITE.getRGB(), -1);
+ yOff += 16;
+ }
+
+ return yOff;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java
new file mode 100644
index 00000000..6469ff1c
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java
@@ -0,0 +1,316 @@
+package io.github.moulberry.notenoughupdates.infopanes;
+
+import info.bliki.htmlcleaner.TagNode;
+import info.bliki.wiki.filter.Encoder;
+import info.bliki.wiki.model.Configuration;
+import info.bliki.wiki.model.ImageFormat;
+import info.bliki.wiki.model.WikiModel;
+import info.bliki.wiki.tags.HTMLBlockTag;
+import info.bliki.wiki.tags.HTMLTag;
+import info.bliki.wiki.tags.IgnoreTag;
+import io.github.moulberry.notenoughupdates.AllowEmptyHTMLTag;
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import io.github.moulberry.notenoughupdates.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.texture.DynamicTexture;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.ResourceLocation;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+public class HTMLInfoPane extends TextInfoPane {
+
+ private static WikiModel wikiModel;
+
+ private final int ZOOM_FACTOR = 2;
+ private final int IMAGE_WIDTH = 400;
+ private final int EXT_WIDTH = 100;
+
+ private ResourceLocation imageTexture = null;
+ private BufferedImage imageTemp = null;
+ private int imageHeight = 0;
+ private int imageWidth = 0;
+
+ static {
+ Configuration conf = new Configuration();
+ conf.addTokenTag("img", new HTMLTag("img"));
+ conf.addTokenTag("code", new HTMLTag("code"));
+ conf.addTokenTag("span", new AllowEmptyHTMLTag("span"));
+ conf.addTokenTag("table", new HTMLBlockTag("table", Configuration.SPECIAL_BLOCK_TAGS+"span|"));
+ conf.addTokenTag("infobox", new IgnoreTag("infobox"));
+ conf.addTokenTag("tabber", new IgnoreTag("tabber"));
+ conf.addTokenTag("kbd", new HTMLTag("kbd"));
+ wikiModel = new WikiModel(conf,"https://hypixel-skyblock.fandom.com/wiki/Special:Filepath/${image}",
+ "https://hypixel-skyblock.fandom.com/wiki/${title}") {
+ {
+ TagNode.addAllowedAttribute("style");
+ TagNode.addAllowedAttribute("src");
+ }
+
+ protected String createImageName(ImageFormat imageFormat) {
+ String imageName = imageFormat.getFilename();
+ if (imageName.endsWith(".svg")) {
+ imageName += ".png";
+ }
+ imageName = Encoder.encodeUrl(imageName);
+ if (replaceColon()) {
+ imageName = imageName.replace(':', '/');
+ }
+ return imageName;
+ }
+
+ public void parseInternalImageLink(String imageNamespace, String rawImageLink) {
+ rawImageLink = rawImageLink.replaceFirst("\\|x([0-9]+)px", "\\|$1x$1px");
+ if(!rawImageLink.split("\\|")[0].toLowerCase().endsWith(".jpg")) {
+ super.parseInternalImageLink(imageNamespace, rawImageLink);
+ }
+ }
+ };
+ }
+
+ public static HTMLInfoPane createFromWikiUrl(NEUOverlay overlay, NEUManager manager, String name, String wikiUrl) {
+ File f = manager.getWebFile(wikiUrl);
+ if(f == null) {
+ return new HTMLInfoPane(overlay, manager, "error", "Failed to load wiki url: "+ wikiUrl);
+ };
+
+ StringBuilder sb = new StringBuilder();
+ try(BufferedReader br = new BufferedReader(new InputStreamReader(
+ new FileInputStream(f), StandardCharsets.UTF_8))) {
+ String l;
+ while((l = br.readLine()) != null){
+ sb.append(l).append("\n");
+ }
+ } catch(IOException e) {
+ return new HTMLInfoPane(overlay, manager, "error", "Failed to load wiki url: "+ wikiUrl);
+ }
+ return createFromWiki(overlay, manager, name, sb.toString());
+ }
+
+ public static HTMLInfoPane createFromWiki(NEUOverlay overlay, NEUManager manager, String name, String wiki) {
+ String[] split = wiki.split("</infobox>");
+ wiki = split[split.length - 1]; //Remove everything before infobox
+ wiki = wiki.split("<span class=\"navbox-vde\">")[0]; //Remove navbox
+ wiki = wiki.split("<table class=\"navbox mw-collapsible\"")[0];
+ wiki = "__NOTOC__\n" + wiki; //Remove TOC
+ try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/parsed.txt"))) {
+ out.println(wiki);
+ } catch (IOException e) {
+ }
+ String html;
+ try {
+ html = wikiModel.render(wiki);
+ } catch(IOException e) {
+ return new HTMLInfoPane(overlay, manager, "error", "Could not render wiki.");
+ }
+ try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/html.txt"))) {
+ out.println(html);
+ } catch (IOException e) {
+ }
+ return new HTMLInfoPane(overlay, manager, name, html);
+ }
+
+ public HTMLInfoPane(NEUOverlay overlay, NEUManager manager, String name, String html) {
+ super(overlay, manager, name, "");
+ this.title = name;
+
+ File cssFile = new File(manager.configLocation, "wikia.css");
+ File wkHtmlToImage = new File(manager.configLocation, "wkhtmltox/bin/wkhtmltoimage");
+ File input = new File(manager.configLocation, "tmp/input.html");
+ String outputFileName = name.replaceAll("(?i)\\u00A7.", "")
+ .replaceAll("[^a-zA-Z0-9_\\-]", "_");
+ File output = new File(manager.configLocation, "tmp/"+
+ outputFileName+".png");
+ File outputExt = new File(manager.configLocation, "tmp/"+
+ outputFileName+"_ext.png");
+
+ input.deleteOnExit();
+ output.deleteOnExit();
+
+ File tmp = new File(manager.configLocation, "tmp");
+ if(!tmp.exists()) {
+ tmp.mkdir();
+ }
+
+ if(output.exists()) {
+ try {
+ imageTemp = ImageIO.read(output);
+ text = EnumChatFormatting.RED+"Creating dynamic texture.";
+ } catch(IOException e) {
+ e.printStackTrace();
+ text = EnumChatFormatting.RED+"Failed to read image.";
+ return;
+ }
+ } else {
+ html = "<div id=\"mw-content-text\" lang=\"en\" dir=\"ltr\" class=\"mw-content-ltr mw-content-text\">"+html+"</div>";
+ html = "<div id=\"WikiaArticle\" class=\"WikiaArticle\">"+html+"</div>";
+ html = "<link rel=\"stylesheet\" href=\"file:///"+cssFile.getAbsolutePath()+"\">\n"+html;
+
+ try(PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(input), StandardCharsets.UTF_8)), false)) {
+
+ out.println(encodeNonAscii(html));
+ } catch(IOException e) {}
+
+
+ ExecutorService ste = Executors.newSingleThreadExecutor();
+ try {
+ text = EnumChatFormatting.GRAY+"Rendering webpage (" + name + EnumChatFormatting.RESET+
+ EnumChatFormatting.GRAY+"), please wait...";
+
+ Runtime runtime = Runtime.getRuntime();
+ Process p = runtime.exec("\""+wkHtmlToImage.getAbsolutePath() + "\" --width "+
+ IMAGE_WIDTH*ZOOM_FACTOR+" --transparent --zoom "+ZOOM_FACTOR+" \"" + input.getAbsolutePath() +
+ "\" \"" + output.getAbsolutePath() + "\"");
+ /*Process p2 = runtime.exec("\""+wkHtmlToImage.getAbsolutePath() + "\" --width "+
+ (IMAGE_WIDTH+EXT_WIDTH)*ZOOM_FACTOR+" --transparent --zoom "+ZOOM_FACTOR+" \"" + input.getAbsolutePath() +
+ "\" \"" + outputExt.getAbsolutePath() + "\"");*/
+ ste.submit(() -> {
+ try {
+ if(p.waitFor(15, TimeUnit.SECONDS)) {
+ //if(p2.waitFor(5, TimeUnit.SECONDS)) {
+ if(overlay.getActiveInfoPane() != this) return;
+
+ try {
+ imageTemp = ImageIO.read(output);
+ /*BufferedImage imageReg = ImageIO.read(output);
+ BufferedImage imageExt = ImageIO.read(outputExt);
+ ArrayList<Integer[]> pixels = new ArrayList<>();
+
+ int skip = IMAGE_WIDTH/EXT_WIDTH+1;
+
+ for(int y=0; y<imageReg.getHeight(); y++) {
+ pixels.add(new Integer[IMAGE_WIDTH*ZOOM_FACTOR]);
+ if(new Color(imageReg.getRGB(IMAGE_WIDTH*ZOOM_FACTOR-1, y), true).getAlpha() == 0) {
+ for(int x=0; x<IMAGE_WIDTH*ZOOM_FACTOR; x++) {
+ pixels.get(y)[x] = imageReg.getRGB(x, y);
+ }
+ } else {
+ for(int x=0; x<(IMAGE_WIDTH+EXT_WIDTH)*ZOOM_FACTOR; x++) {
+ int x2 = x*IMAGE_WIDTH/(IMAGE_WIDTH+EXT_WIDTH);
+ int y2 = y*(IMAGE_WIDTH+EXT_WIDTH)/IMAGE_WIDTH;
+ pixels.get(y)[x2] = imageExt.getRGB(x, y2);
+ }
+ }
+ }
+ imageTemp = new BufferedImage(IMAGE_WIDTH*ZOOM_FACTOR, pixels.size(), imageReg.getType());
+ for(int y=0; y<pixels.size(); y++) {
+ for(int x=0; x<IMAGE_WIDTH*ZOOM_FACTOR; x++) {
+ int col = pixels.get(y)[x];
+ imageTemp.setRGB(x, y, col);
+ }
+ }*/
+ text = EnumChatFormatting.RED+"Creating dynamic texture.";
+ } catch(IOException e) {
+ e.printStackTrace();
+ text = EnumChatFormatting.RED+"Failed to read image.";
+ return;
+ }
+ } else {
+ if(overlay.getActiveInfoPane() != this) return;
+
+ text = EnumChatFormatting.RED+"Webpage render timed out (>15sec). Maybe it's too large?";
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ });
+ } catch(IOException e) {
+ e.printStackTrace();
+ text = EnumChatFormatting.RED+"Failed to exec webpage renderer.";
+ } finally {
+ ste.shutdown();
+ }
+ }
+ }
+
+ @Override
+ public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, int mouseY) {
+ if(imageTemp != null && imageTexture == null) {
+ DynamicTexture tex = new DynamicTexture(imageTemp);
+ imageTexture = Minecraft.getMinecraft().getTextureManager().getDynamicTextureLocation(
+ "notenoughupdates/informationPaneImage", tex);
+ imageHeight = imageTemp.getHeight();
+ imageWidth = imageTemp.getWidth();
+ }
+ if(imageTexture == null) {
+ super.render(width, height, bg, fg, scaledresolution, mouseX, mouseY);
+ return;
+ }
+
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+
+ int titleLen = fr.getStringWidth(title);
+ fr.drawString(title, (leftSide+rightSide-titleLen)/2, overlay.BOX_PADDING + 5, Color.WHITE.getRGB());
+
+ drawRect(leftSide+overlay.BOX_PADDING-5, overlay.BOX_PADDING-5, rightSide-overlay.BOX_PADDING+5,
+ height-overlay.BOX_PADDING+5, bg.getRGB());
+
+ int imageW = paneWidth - overlay.BOX_PADDING*2;
+ float scaleF = IMAGE_WIDTH*ZOOM_FACTOR/(float)imageW;
+
+ Minecraft.getMinecraft().getTextureManager().bindTexture(imageTexture);
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ if(height-overlay.BOX_PADDING*3 < imageHeight/scaleF) {
+ if(scrollHeight.getValue() > imageHeight/scaleF-height+overlay.BOX_PADDING*3) {
+ scrollHeight.setValue((int)(imageHeight/scaleF-height+overlay.BOX_PADDING*3));
+ }
+ int yScroll = scrollHeight.getValue();
+
+ float vMin = yScroll/(imageHeight/scaleF);
+ float vMax = (yScroll+height-overlay.BOX_PADDING*3)/(imageHeight/scaleF);
+ Utils.drawTexturedRect(leftSide+overlay.BOX_PADDING, overlay.BOX_PADDING*2, imageW,
+ height-overlay.BOX_PADDING*3,
+ 0, 1, vMin, vMax);
+ } else {
+ scrollHeight.setValue(0);
+
+ Utils.drawTexturedRect(leftSide+overlay.BOX_PADDING, overlay.BOX_PADDING*2, imageW,
+ (int)(imageHeight/scaleF));
+ }
+ GlStateManager.bindTexture(0);
+ }
+
+ @Override
+ public void keyboardInput() {
+ }
+
+ @Override
+ public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) {
+ super.mouseInput(width, height, mouseX, mouseY, mouseDown);
+ }
+
+ //From https://stackoverflow.com/questions/1760766/how-to-convert-non-supported-character-to-html-entity-in-java
+ public String encodeNonAscii(String c) {
+ StringBuilder buf = new StringBuilder(c.length());
+ CharsetEncoder enc = StandardCharsets.US_ASCII.newEncoder();
+ for (int idx = 0; idx < c.length(); ++idx) {
+ char ch = c.charAt(idx);
+ if (enc.canEncode(ch))
+ buf.append(ch);
+ else {
+ buf.append("&#");
+ buf.append((int) ch);
+ buf.append(';');
+ }
+ }
+ return buf.toString();
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/InfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/InfoPane.java
new file mode 100644
index 00000000..74321081
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/InfoPane.java
@@ -0,0 +1,44 @@
+package io.github.moulberry.notenoughupdates.infopanes;
+
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.ScaledResolution;
+
+import java.awt.*;
+
+public abstract class InfoPane extends Gui {
+
+ final NEUOverlay overlay;
+ final NEUManager manager;
+
+ public InfoPane(NEUOverlay overlay, NEUManager manager) {
+ this.overlay = overlay;
+ this.manager = manager;
+ }
+
+ public void tick() {}
+
+ public abstract void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX,
+ int mouseY);
+
+ public abstract void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown);
+
+ public abstract void keyboardInput();
+
+ public void renderDefaultBackground(int width, int height, Color bg) {
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+
+ int boxLeft = leftSide + overlay.BOX_PADDING - 5;
+ int boxRight = rightSide - overlay.BOX_PADDING + 5;
+
+ overlay.renderBlurredBackground(width, height,
+ boxLeft, overlay.BOX_PADDING-5,
+ boxRight-boxLeft, height-overlay.BOX_PADDING*2+10);
+ drawRect(boxLeft, overlay.BOX_PADDING - 5, boxRight,
+ height - overlay.BOX_PADDING + 5, bg.getRGB());
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/QOLInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/QOLInfoPane.java
new file mode 100644
index 00000000..bf1a987e
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/QOLInfoPane.java
@@ -0,0 +1,344 @@
+package io.github.moulberry.notenoughupdates.infopanes;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import io.github.moulberry.notenoughupdates.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompressedStreamTools;
+import net.minecraft.nbt.JsonToNBT;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.ResourceLocation;
+import org.lwjgl.input.Keyboard;
+
+import java.awt.*;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.*;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class QOLInfoPane extends ScrollableInfoPane {
+ private LinkedHashMap<String, ItemStack> accessoryMap = new LinkedHashMap<>();
+ private LinkedHashMap<String, JsonObject> allTalismans = new LinkedHashMap<>();
+ private List<JsonObject> recommended = new ArrayList<>();
+ private int maxAccessories = 39; //TODO: Get from API
+
+ private Integer[] talismanRarityValue = new Integer[]{1, 2, 4, 7, 10};
+
+ public QOLInfoPane(NEUOverlay overlay, NEUManager manager) {
+ super(overlay, manager);
+
+ Comparator<JsonObject> rarityComparator = new Comparator<JsonObject>() {
+ @Override
+ public int compare(JsonObject o1, JsonObject o2) {
+ int rarity1 = overlay.getRarity(o1.get("lore").getAsJsonArray());
+ int rarity2 = overlay.getRarity(o2.get("lore").getAsJsonArray());
+
+ int rarityDiff = rarity2 - rarity1;
+ if(rarityDiff != 0) {
+ return rarityDiff;
+ }
+
+ return o2.get("internalname").getAsString().compareTo(o1.get("internalname").getAsString());
+ }
+ };
+
+ TreeSet<JsonObject> all = new TreeSet<>(rarityComparator);
+ LinkedHashMap<String, JsonObject> highestRarity = new LinkedHashMap<>();
+ LinkedHashMap<String, JsonObject> lowerRarity = new LinkedHashMap<>();
+
+ for(Map.Entry<String, JsonObject> entry : manager.getItemInformation().entrySet()) {
+ if(overlay.checkItemType(entry.getValue().get("lore").getAsJsonArray(), "ACCESSORY") >= 0) {
+ all.add(entry.getValue());
+ }
+ }
+ outer:
+ for(JsonObject o : all) {
+ String internalname = o.get("internalname").getAsString();
+ String name = getTalismanName(internalname);
+ int power = getTalismanPower(internalname);
+ for(JsonObject o2 : all) {
+ if(o != o2) {
+ String internalname2 = o2.get("internalname").getAsString();
+ String name2 = getTalismanName(internalname2);
+ if(name2.equals(name)) {
+ int power2 = getTalismanPower(internalname2);
+ if(power2 > power) {
+ lowerRarity.put(internalname, o);
+ continue outer;
+ }
+ }
+ }
+ }
+ highestRarity.put(internalname, o);
+ }
+ for(Map.Entry<String, JsonObject> entry : highestRarity.entrySet()) {
+ allTalismans.put(entry.getKey(), entry.getValue());
+ }
+ for(Map.Entry<String, JsonObject> entry : lowerRarity.entrySet()) {
+ allTalismans.put(entry.getKey(), entry.getValue());
+ }
+
+ HashMap<String, String> args = new HashMap<>();
+ String uuid = Minecraft.getMinecraft().thePlayer.getGameProfile().getId().toString();
+ args.put("uuid", uuid);
+ manager.hypixelApi.getHypixelApiAsync(manager.config.apiKey.value, "skyblock/profiles",
+ args, jsonObject -> {
+ if(jsonObject.get("success").getAsBoolean()) {
+ JsonObject currProfile = null;
+ for(JsonElement e : jsonObject.get("profiles").getAsJsonArray()) {
+ JsonObject profile = e.getAsJsonObject();
+ String profileId = profile.get("profile_id").getAsString();
+ String cuteName = profile.get("cute_name").getAsString();
+
+ if(manager.currentProfile.equals(cuteName)) {
+ JsonObject members = profile.get("members").getAsJsonObject();
+ JsonObject profile_member = members.get(uuid.replaceAll("-","")).getAsJsonObject();
+ currProfile = profile_member;
+ }
+ }
+ if(currProfile.has("talisman_bag")) {
+ String b64 = currProfile.get("talisman_bag").getAsJsonObject().get("data").getAsString();
+ try {
+ NBTTagCompound tag = CompressedStreamTools.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(b64)));
+ NBTTagList list = tag.getTagList("i", 10);
+ for(int i=0; i<list.tagCount(); i++) {
+ NBTTagCompound accessory = list.getCompoundTagAt(i);
+ if(accessory.hasKey("tag")) {
+ String accessoryID = accessory.getCompoundTag("tag")
+ .getCompoundTag("ExtraAttributes").getString("id");
+ ItemStack accessoryStack = ItemStack.loadItemStackFromNBT(accessory);
+ accessoryMap.put(accessoryID, accessoryStack);
+ }
+
+ }
+ } catch(IOException e) {
+ }
+
+ int lowestRarity = -1;
+ if(accessoryMap.size() >= maxAccessories) {
+ lowestRarity = 999;
+ for(Map.Entry<String, ItemStack> entry : accessoryMap.entrySet()) {
+ JsonObject json = manager.getJsonForItem(entry.getValue());
+ int rarity = overlay.getRarity(json.get("lore").getAsJsonArray());
+ if(rarity < lowestRarity) {
+ lowestRarity = rarity;
+ }
+ }
+ }
+ System.out.println("lowestrarity:"+lowestRarity);
+
+ TreeMap<Float, JsonObject> valueMap = new TreeMap<>();
+ outer:
+ for(Map.Entry<String, JsonObject> entry : allTalismans.entrySet()) {
+ int rarity = overlay.getRarity(entry.getValue().get("lore").getAsJsonArray());
+ System.out.println(entry.getKey() + ":" + rarity);
+ if(rarity > lowestRarity) {
+ System.out.println("found greater:"+entry.getKey());
+ float rarityVal = (float)talismanRarityValue[rarity];
+ System.out.println("rarity val:"+rarityVal);
+ float price = manager.getCraftCost(entry.getKey()).craftCost;
+
+ System.out.println("cc:"+price);
+ if(price < 0) {
+ System.out.println("invalid price:"+entry.getKey());
+ continue;
+ }
+
+ String internalname = entry.getValue().get("internalname").getAsString();
+ String name = getTalismanName(internalname);
+ int power = getTalismanPower(internalname);
+ for(Map.Entry<String, ItemStack> entry2 : accessoryMap.entrySet()) {
+ try {
+ JsonObject json = manager.getJsonForItem(entry2.getValue());
+ String internalname2 = json.get("internalname").getAsString();
+
+ if(internalname.equals(internalname2)) {
+ //continue outer;
+ }
+
+ String name2 = getTalismanName(internalname2);
+ } catch(Exception e) {
+ e.printStackTrace();
+ System.out.println(":( -> " + entry2.getKey());
+ }
+
+
+ /*if(name2.equals(name)) {
+ int power2 = getTalismanPower(internalname2);
+ if(power2 > power) {
+ continue outer;
+ }
+ }*/
+ }
+
+ valueMap.put(-rarityVal/price, entry.getValue());
+ }
+ }
+ System.out.println("valuemap size:"+valueMap.size());
+ int i=0;
+ for(Map.Entry<Float, JsonObject> entry : valueMap.entrySet()) {
+ recommended.add(entry.getValue());
+ if(++i >= 500) {
+ break;
+ }
+ }
+ System.out.println("recommended size:"+recommended.size());
+ }
+ //jsonObject.get("profiles")
+ }
+ });
+ }
+
+
+ String[] talismanPowers = new String[]{"RING","ARTIFACT"};
+ public int getTalismanPower(String internalname) {
+ for(int i=0; i<talismanPowers.length; i++) {
+ if(internalname.endsWith("_"+talismanPowers[i])) {
+ return i+1;
+ }
+ }
+ return 0;
+ }
+
+ public String getTalismanName(String internalname) {
+ String[] split = internalname.split("_");
+ StringBuilder name = new StringBuilder();
+ for(int i=0; i<split.length; i++) {
+ name.append(split[i]);
+ if(i < split.length-1) {
+ name.append("_");
+ }
+ }
+ return name.toString();
+ }
+
+ public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, int mouseY) {
+ renderDefaultBackground(width, height, bg);
+
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+
+ int y=overlay.BOX_PADDING+10;
+ y += renderParagraph(width, height, y, "Current Accessories");
+
+ ItemStack display = null;
+ int x=leftSide+overlay.BOX_PADDING+5;
+ for(Map.Entry<String, ItemStack> entry : accessoryMap.entrySet()) {
+ if(mouseX > x && mouseX < x+16) {
+ if(mouseY > y && mouseY < y+16) {
+ display = entry.getValue();
+ }
+ }
+
+ drawRect(x, y, x+16, y+16, fg.getRGB());
+ Utils.drawItemStack(entry.getValue(), x, y);
+ x += 20;
+ if(x + 20 + (leftSide+overlay.BOX_PADDING+5) > paneWidth) {
+ x=leftSide+overlay.BOX_PADDING+5;
+ y+=20;
+ }
+ }
+
+ y+=20;
+
+ y += renderParagraph(width, height, y, "Missing Accessories");
+
+ y+=10;
+
+ x=leftSide+overlay.BOX_PADDING+5;
+ for(Map.Entry<String, JsonObject> entry : allTalismans.entrySet()) {
+ if(accessoryMap.containsKey(entry.getKey())) {
+ continue;
+ }
+ if(mouseX > x && mouseX < x+16) {
+ if(mouseY > y && mouseY < y+16) {
+ display = manager.jsonToStack(entry.getValue());
+ }
+ }
+
+ drawRect(x, y, x+16, y+16, fg.getRGB());
+ Utils.drawItemStack(manager.jsonToStack(entry.getValue()), x, y);
+ x += 20;
+ if(x + 20 + (leftSide+overlay.BOX_PADDING+5) > paneWidth) {
+ x=leftSide+overlay.BOX_PADDING+5;
+ y+=20;
+ }
+ }
+
+ y+=20;
+ y += renderParagraph(width, height, y, "Recommended Accessory Upgrades");
+
+ x=leftSide+overlay.BOX_PADDING+5;
+ for(JsonObject json : recommended) {
+ if(mouseX > x && mouseX < x+16) {
+ if(mouseY > y && mouseY < y+16) {
+ display = manager.jsonToStack(json);
+ }
+ }
+
+ drawRect(x, y, x+16, y+16, fg.getRGB());
+ Utils.drawItemStack(manager.jsonToStack(json), x, y);
+ x += 20;
+ if(x + 20 + (leftSide+overlay.BOX_PADDING+5) > paneWidth) {
+ x=leftSide+overlay.BOX_PADDING+5;
+ y+=20;
+ }
+ }
+
+ //L:9/cost, E=6/cost, R=3/cost, C=1/cost
+
+
+ if(display != null) {
+ List<String> list = display.getTooltip(Minecraft.getMinecraft().thePlayer,
+ Minecraft.getMinecraft().gameSettings.advancedItemTooltips);
+
+ for (int i = 0; i < list.size(); ++i){
+ if (i == 0){
+ list.set(i, display.getRarity().rarityColor + list.get(i));
+ } else {
+ list.set(i, EnumChatFormatting.GRAY + list.get(i));
+ }
+ }
+
+ Utils.drawHoveringText(list, mouseX, mouseY, width, height, -1, Minecraft.getMinecraft().fontRendererObj);
+ }
+ }
+
+ public void keyboardInput() {
+
+ }
+
+
+ private int renderParagraph(int width, int height, int startY, String text) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+
+ int yOff = 0;
+ for(String line : text.split("\n")) {
+ yOff += Utils.renderStringTrimWidth(line, fr, false,leftSide+overlay.BOX_PADDING + 5,
+ startY + yOff,
+ width*1/3-overlay.BOX_PADDING*2-10, Color.WHITE.getRGB(), -1);
+ yOff += 16;
+ }
+
+ return yOff;
+ }
+
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/ScrollableInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/ScrollableInfoPane.java
new file mode 100644
index 00000000..056aeaf1
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/ScrollableInfoPane.java
@@ -0,0 +1,34 @@
+package io.github.moulberry.notenoughupdates.infopanes;
+
+import io.github.moulberry.notenoughupdates.util.LerpingInteger;
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import org.lwjgl.input.Mouse;
+
+public abstract class ScrollableInfoPane extends InfoPane {
+
+ private static final int SCROLL_AMOUNT = 50;
+ protected LerpingInteger scrollHeight = new LerpingInteger(0);
+
+ public ScrollableInfoPane(NEUOverlay overlay, NEUManager manager) {
+ super(overlay, manager);
+ }
+
+ public void tick() {
+ scrollHeight.tick();
+ if(scrollHeight.getValue() < 0) scrollHeight.setValue(0);
+ }
+
+ @Override
+ public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) {
+ int dWheel = Mouse.getEventDWheel();
+
+ if(dWheel < 0) {
+ scrollHeight.setTarget(scrollHeight.getTarget()+SCROLL_AMOUNT);
+ scrollHeight.resetTimer();
+ } else if(dWheel > 0) {
+ scrollHeight.setTarget(scrollHeight.getTarget()-SCROLL_AMOUNT);
+ scrollHeight.resetTimer();
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/SettingsInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/SettingsInfoPane.java
new file mode 100644
index 00000000..31f6dca9
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/SettingsInfoPane.java
@@ -0,0 +1,259 @@
+package io.github.moulberry.notenoughupdates.infopanes;
+
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import io.github.moulberry.notenoughupdates.Utils;
+import io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField;
+import io.github.moulberry.notenoughupdates.options.Options;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.util.EnumChatFormatting;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import static io.github.moulberry.notenoughupdates.GuiTextures.*;
+
+public class SettingsInfoPane extends InfoPane {
+
+ private final Map<Options.Option<?>, GuiElementTextField> textConfigMap = new HashMap<>();
+ private int page = 0;
+ private int maxPages = 1;
+
+ public SettingsInfoPane(NEUOverlay overlay, NEUManager manager) {
+ super(overlay, manager);
+ }
+
+ public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX,
+ int mouseY) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+
+ this.renderDefaultBackground(width, height, bg);
+
+ if(page > maxPages-1) page = maxPages-1;
+ if(page < 0) page = 0;
+
+ overlay.renderNavElement(leftSide+overlay.BOX_PADDING, rightSide-overlay.BOX_PADDING,
+ maxPages,page+1,"Settings: ");
+
+ AtomicReference<List<String>> textToDisplay = new AtomicReference<>(null);
+ AtomicReference<GuiElementTextField> tfTop = new AtomicReference<>();
+ AtomicInteger tfTopX = new AtomicInteger();
+ AtomicInteger tfTopY = new AtomicInteger();
+ iterateSettingTile(new SettingsTileConsumer() {
+ public void consume(int x, int y, int tileWidth, int tileHeight, Options.Option<?> option) {
+ float mult = tileWidth/90f;
+
+ drawRect(x, y, x+tileWidth, y+tileHeight, fg.getRGB());
+ if(scaledresolution.getScaleFactor()==4) {
+ GL11.glScalef(0.5f,0.5f,1);
+ Utils.renderStringTrimWidth(option.displayName, fr, true, (x+(int)(8*mult))*2, (y+(int)(8*mult))*2,
+ (tileWidth-(int)(16*mult))*2, new Color(100,255,150).getRGB(), 3);
+ GL11.glScalef(2,2,1);
+ } else {
+ Utils.renderStringTrimWidth(option.displayName, fr, true, x+(int)(8*mult), y+(int)(8*mult),
+ tileWidth-(int)(16*mult), new Color(100,255,150).getRGB(), 3);
+ }
+
+ if(Keyboard.isKeyDown(Keyboard.KEY_H)) return;
+
+ if(option.value instanceof Boolean) {
+ GlStateManager.color(1f, 1f, 1f, 1f);
+ Minecraft.getMinecraft().getTextureManager().bindTexture(((Boolean)option.value) ? on : off);
+ Utils.drawTexturedRect(x+tileWidth/2-(int)(32*mult), y+tileHeight-(int)(20*mult), (int)(48*mult), (int)(16*mult));
+
+ Minecraft.getMinecraft().getTextureManager().bindTexture(help);
+ Utils.drawTexturedRect(x+tileWidth/2+(int)(19*mult), y+tileHeight-(int)(19*mult), (int)(14*mult), (int)(14*mult));
+ GlStateManager.bindTexture(0);
+
+ if(mouseX > x+tileWidth/2+(int)(19*mult) && mouseX < x+tileWidth/2+(int)(19*mult)+(int)(14*mult)) {
+ if(mouseY > y+tileHeight-(int)(19*mult) && mouseY < y+tileHeight-(int)(19*mult)+(int)(14*mult)) {
+ List<String> textLines = new ArrayList<>();
+ textLines.add(option.displayName);
+ textLines.add(EnumChatFormatting.GRAY+option.desc);
+ textToDisplay.set(textLines);
+ }
+ }
+ } else {
+ if(!textConfigMap.containsKey(option)) {
+ textConfigMap.put(option, new GuiElementTextField(String.valueOf(option.value), 0));
+ }
+ GuiElementTextField tf = textConfigMap.get(option);
+ if(tf.getFocus()) {
+ tf.setSize(Math.max(tileWidth-(int)(20*mult), fr.getStringWidth(tf.getText())+10), (int)(16*mult));
+ tfTop.set(tf);
+ tfTopX.set(x+(int)(10*mult));
+ tfTopY.set(y+tileHeight-(int)(20*mult));
+ } else {
+ tf.setSize(tileWidth-(int)(20*mult), (int)(16*mult));
+ tf.render(x+(int)(10*mult), y+tileHeight-(int)(20*mult));
+ }
+ }
+ }
+ });
+ if(tfTop.get() != null) {
+ tfTop.get().render(tfTopX.get(), tfTopY.get());
+ }
+ if(textToDisplay.get() != null) {
+ Utils.drawHoveringText(textToDisplay.get(), mouseX, mouseY, width, height, 200, fr);
+ }
+ }
+
+ private void onTextfieldChange(GuiElementTextField tf, Options.Option<?> option) {
+ try {
+ tf.setCustomBorderColour(-1);
+ option.setValue(tf.getText());
+ overlay.redrawItems();
+ } catch(Exception e) {
+ tf.setCustomBorderColour(Color.RED.getRGB());
+ }
+ }
+
+ public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) {
+ iterateSettingTile(new SettingsTileConsumer() {
+ @Override
+ public void consume(int x, int y, int tileWidth, int tileHeight, Options.Option<?> option) {
+ float mult = tileWidth/90f;
+ if(option.value instanceof Boolean) {
+ if(Mouse.getEventButtonState()) {
+ if(mouseX > x+tileWidth/2-(int)(32*mult) && mouseX < x+tileWidth/2-(int)(32*mult)+(int)(48*mult)) {
+ if(mouseY > y+tileHeight-(int)(20*mult) && mouseY < y+tileHeight-(int)(20*mult)+(int)(16*mult)) {
+ ((Options.Option<Boolean>)option).value = !((Boolean)option.value);
+ overlay.redrawItems();
+ return;
+ }
+ }
+ }
+ } else {
+ if(!textConfigMap.containsKey(option)) {
+ textConfigMap.put(option, new GuiElementTextField(String.valueOf(option.value), 0));
+ }
+ GuiElementTextField tf = textConfigMap.get(option);
+ if(mouseX > x+(int)(10*mult) && mouseX < x+(int)(10*mult)+tileWidth-(int)(20*mult)) {
+ if(mouseY > y+tileHeight-(int)(20*mult) && mouseY < y+tileHeight-(int)(20*mult)+(int)(16*mult)) {
+ if(Mouse.getEventButtonState()) {
+ tf.mouseClicked(mouseX, mouseY, Mouse.getEventButton());
+ onTextfieldChange(tf, option);
+ return;
+ } else if(Mouse.getEventButton() == -1 && mouseDown) {
+ tf.mouseClickMove(mouseX, mouseY, 0, 0); //last 2 values are unused
+ return;
+ }
+ }
+ }
+ if(Mouse.getEventButtonState()) tf.otherComponentClick();
+ }
+ }
+ });
+
+ if(Mouse.getEventButtonState()) {
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+ rightSide -= overlay.BOX_PADDING;
+ leftSide += overlay.BOX_PADDING;
+
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+ float maxStrLen = fr.getStringWidth(EnumChatFormatting.BOLD+"Settings: " + maxPages + "/" + maxPages);
+ float maxButtonXSize = (rightSide-leftSide+2 - maxStrLen*0.5f - 10)/2f;
+ int buttonXSize = (int)Math.min(maxButtonXSize, overlay.getSearchBarYSize()*480/160f);
+ int ySize = (int)(buttonXSize/480f*160);
+ int yOffset = (int)((overlay.getSearchBarYSize()-ySize)/2f);
+ int top = overlay.BOX_PADDING+yOffset;
+
+ if(mouseY >= top && mouseY <= top+ySize) {
+ int leftPrev = leftSide-1;
+ if(mouseX > leftPrev && mouseX < leftPrev+buttonXSize) { //"Previous" button
+ page--;
+ }
+ int leftNext = rightSide+1-buttonXSize;
+ if(mouseX > leftNext && mouseX < leftNext+buttonXSize) { //"Next" button
+ page++;
+ }
+ }
+ }
+ }
+
+ public void keyboardInput() {
+ iterateSettingTile(new SettingsTileConsumer() {
+ @Override
+ public void consume(int x, int y, int tileWidth, int tileHeight, Options.Option<?> option) {
+ if(!textConfigMap.containsKey(option)) {
+ textConfigMap.put(option, new GuiElementTextField(String.valueOf(option.value), 0));
+ }
+ GuiElementTextField tf = textConfigMap.get(option);
+
+ if(!(option.value instanceof Boolean)) {
+ if(tf.getFocus()) {
+ tf.keyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey());
+ onTextfieldChange(tf, option);
+ }
+ }
+ }
+ });
+ }
+
+ private abstract static class SettingsTileConsumer {
+ public abstract void consume(int x, int y, int tileWidth, int tileHeight, Options.Option<?> option);
+ }
+
+ public void iterateSettingTile(SettingsTileConsumer settingsTileConsumer) {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int width = scaledresolution.getScaledWidth();
+ int height = scaledresolution.getScaledHeight();
+
+ int numHorz = scaledresolution.getScaleFactor() >= 3 ? 2 : 3;
+
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+
+ int boxLeft = leftSide+overlay.BOX_PADDING-5;
+ int boxRight = rightSide-overlay.BOX_PADDING+5;
+
+ int boxBottom = height - overlay.BOX_PADDING + 5;
+
+ int boxWidth = boxRight-boxLeft;
+ int tilePadding = 7;
+ int tileWidth = (boxWidth-tilePadding*4)/numHorz;
+ int tileHeight = tileWidth*3/4;
+
+ maxPages=1;
+ int currPage=0;
+ int x=0;
+ int y=tilePadding+overlay.BOX_PADDING+overlay.getSearchBarYSize();
+ for(int i=0; i<manager.config.getOptions().size(); i++) {
+ if(i!=0 && i%numHorz==0) {
+ x = 0;
+ y += tileHeight+tilePadding;
+ }
+ if(y + tileHeight > boxBottom) {
+ x=0;
+ y=tilePadding+overlay.BOX_PADDING+overlay.getSearchBarYSize();
+ currPage++;
+ maxPages = currPage+1;
+ }
+ x+=tilePadding;
+
+ if(currPage == page) {
+ settingsTileConsumer.consume(boxLeft+x, y, tileWidth, tileHeight, manager.config.getOptions().get(i));
+ }
+
+ x+=tileWidth;
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/TextInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/TextInfoPane.java
new file mode 100644
index 00000000..388719d0
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/TextInfoPane.java
@@ -0,0 +1,61 @@
+package io.github.moulberry.notenoughupdates.infopanes;
+
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import io.github.moulberry.notenoughupdates.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.ScaledResolution;
+
+import java.awt.*;
+
+public class TextInfoPane extends ScrollableInfoPane {
+
+ protected String title;
+ protected String text;
+
+ public TextInfoPane(NEUOverlay overlay, NEUManager manager, String title, String text) {
+ super(overlay, manager);
+ this.title = title;
+ this.text = text;
+ }
+
+ public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, int mouseY) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ int paneWidth = (int)(width/3*overlay.getWidthMult());
+ int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor());
+ int leftSide = rightSide - paneWidth;
+
+ int titleLen = fr.getStringWidth(title);
+ int yScroll = -scrollHeight.getValue();
+ fr.drawString(title, (leftSide+rightSide-titleLen)/2, yScroll+overlay.BOX_PADDING + 5,
+ Color.WHITE.getRGB());
+
+ int yOff = 20;
+ for(String line : text.split("\n")) {
+ yOff += Utils.renderStringTrimWidth(line, fr, false,leftSide+overlay.BOX_PADDING + 5,
+ yScroll+overlay.BOX_PADDING + 10 + yOff,
+ width*1/3-overlay.BOX_PADDING*2-10, Color.WHITE.getRGB(), -1);
+ yOff += 16;
+ }
+
+ int top = overlay.BOX_PADDING - 5;
+ int totalBoxHeight = yOff+14;
+ int bottom = Math.max(top+totalBoxHeight, height-overlay.BOX_PADDING+5);
+
+ if(scrollHeight.getValue() > top+totalBoxHeight-(height-overlay.BOX_PADDING+5)) {
+ scrollHeight.setValue(top+totalBoxHeight-(height-overlay.BOX_PADDING+5));
+ }
+ drawRect(leftSide+overlay.BOX_PADDING-5, yScroll+overlay.BOX_PADDING-5,
+ rightSide-overlay.BOX_PADDING+5, yScroll+bottom, bg.getRGB());
+ }
+
+ public void keyboardInput() {
+
+ }
+
+ public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) {
+ super.mouseInput(width, height, mouseX, mouseY, mouseDown);
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElement.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElement.java
new file mode 100644
index 00000000..06a8810d
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElement.java
@@ -0,0 +1,15 @@
+package io.github.moulberry.notenoughupdates.itemeditor;
+
+import net.minecraft.client.gui.Gui;
+
+public abstract class GuiElement extends Gui {
+
+ public abstract void render(int x, int y);
+ public abstract int getWidth();
+ public abstract int getHeight();
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {}
+ public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {}
+ public void otherComponentClick() {}
+ public void keyTyped(char typedChar, int keyCode) {}
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementButton.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementButton.java
new file mode 100644
index 00000000..0ed03c36
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementButton.java
@@ -0,0 +1,35 @@
+package io.github.moulberry.notenoughupdates.itemeditor;
+
+import java.awt.*;
+
+public class GuiElementButton extends GuiElementText {
+
+ private Runnable callback;
+
+ public GuiElementButton(String text, int colour, Runnable callback) {
+ super(text, colour);
+ this.callback = callback;
+ }
+
+ @Override
+ public int getHeight() {
+ return super.getHeight() + 5;
+ }
+
+ @Override
+ public int getWidth() {
+ return super.getWidth() + 10;
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ callback.run();
+ }
+
+ @Override
+ public void render(int x, int y) {
+ drawRect(x, y, x+getWidth(), y+super.getHeight(), Color.WHITE.getRGB());
+ drawRect(x+1, y+1, x+getWidth()-1, y+super.getHeight()-1, Color.BLACK.getRGB());
+ super.render(x+5, y-1);
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementText.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementText.java
new file mode 100644
index 00000000..28bc9b71
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementText.java
@@ -0,0 +1,42 @@
+package io.github.moulberry.notenoughupdates.itemeditor;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+
+public class GuiElementText extends GuiElement {
+
+ protected String text;
+ private int colour;
+
+ public GuiElementText(String text, int colour) {
+ this.text = text;
+ this.colour = colour;
+ }
+
+ @Override
+ public int getHeight() {
+ return 18;
+ }
+
+ @Override
+ public int getWidth() {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+ return fr.getStringWidth(text);
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ @Override
+ public void render(int x, int y) {
+ FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
+
+ fr.drawString(text, x, y+6, colour);
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java
new file mode 100644
index 00000000..1112a882
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java
@@ -0,0 +1,453 @@
+package io.github.moulberry.notenoughupdates.itemeditor;
+
+import com.google.common.base.Predicate;
+import io.github.moulberry.notenoughupdates.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.GuiTextField;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.util.EnumChatFormatting;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.annotation.Nullable;
+import java.awt.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GuiElementTextField extends GuiElement {
+
+ public static final int NUM_ONLY = 0b10000;
+ public static final int NO_SPACE = 0b01000;
+ public static final int FORCE_CAPS = 0b00100;
+ public static final int COLOUR = 0b00010;
+ public static final int MULTILINE = 0b00001;
+
+ private int searchBarYSize = 20;
+ private int searchBarXSize = 350;
+ private static final int searchBarPadding = 2;
+
+ private int options = 0;
+
+ private boolean focus = false;
+
+ private int x;
+ private int y;
+
+ private GuiTextField textField = new GuiTextField(0, Minecraft.getMinecraft().fontRendererObj,
+ 0 , 0, 0, 0);
+
+ private int customBorderColour = -1;
+
+ public GuiElementTextField(String initialText, int options) {
+ textField.setFocused(true);
+ textField.setCanLoseFocus(false);
+ textField.setMaxStringLength(999);
+ textField.setText(initialText);
+ this.options = options;
+ }
+
+ public void setCustomBorderColour(int colour) {
+ this.customBorderColour = colour;
+ }
+
+ public String getText() {
+ return textField.getText();
+ }
+
+ public void setSize(int searchBarXSize, int searchBarYSize) {
+ this.searchBarXSize = searchBarXSize;
+ this.searchBarYSize = searchBarYSize;
+ }
+
+ @Override
+ public String toString() {
+ return textField.getText();
+ }
+
+ public boolean getFocus() {
+ return focus;
+ }
+
+ @Override
+ public int getHeight() {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor();
+
+ int numLines = StringUtils.countMatches(textField.getText(), "\n")+1;
+ int extraSize = (searchBarYSize-8)/2+8;
+ int bottomTextBox = searchBarYSize + extraSize*(numLines-1);
+
+ return bottomTextBox + paddingUnscaled*2;
+ }
+
+ @Override
+ public int getWidth() {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor();
+
+ return searchBarXSize + paddingUnscaled*2;
+ }
+
+ public int getCursorPos(int mouseX, int mouseY) {
+ int xComp = mouseX - x;
+ int yComp = mouseY - y;
+
+ int extraSize = (searchBarYSize-8)/2+8;
+
+ int lineNum = Math.round(((yComp - (searchBarYSize-8)/2))/extraSize);
+
+ Pattern patternControlCode = Pattern.compile("(?i)\\u00A7([^\\u00B6])(?!\\u00B6)");
+ String text = textField.getText();
+ String textNoColour = textField.getText();
+ while(true) {
+ Matcher matcher = patternControlCode.matcher(text);
+ if(!matcher.find() || matcher.groupCount() < 1) break;
+ String code = matcher.group(1);
+ text = matcher.replaceFirst("\u00A7"+code+"\u00B6"+code);
+ }
+ while(true) {
+ Matcher matcher = patternControlCode.matcher(textNoColour);
+ if(!matcher.find() || matcher.groupCount() < 1) break;
+ String code = matcher.group(1);
+ textNoColour = matcher.replaceFirst("\u00B6"+code);
+ }
+
+ int currentLine = 0;
+ int cursorIndex = 0;
+ for(; cursorIndex<textNoColour.length(); cursorIndex++) {
+ if(currentLine == lineNum) break;
+ if(textNoColour.charAt(cursorIndex) == '\n') {
+ currentLine++;
+ }
+ }
+ String textNC = textNoColour.substring(0, cursorIndex);
+ int colorCodes = StringUtils.countMatches(textNC, "\u00B6");
+ String line = text.substring(cursorIndex+colorCodes*2).split("\n")[0];
+ String trimmed = Minecraft.getMinecraft().fontRendererObj.trimStringToWidth(line, xComp-5);
+ int linePos = strLenNoColor(trimmed);
+ if(linePos != strLenNoColor(line)) {
+ char after = line.charAt(linePos);
+ int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed);
+ int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after);
+ if(trimmedWidth + charWidth/2 < xComp-5) {
+ linePos++;
+ }
+ }
+ cursorIndex += linePos;
+ return cursorIndex;
+ }
+
+ @Override
+ public void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ if(mouseButton == 1) {
+ textField.setText("");
+ } else {
+ textField.setCursorPosition(getCursorPos(mouseX, mouseY));
+ }
+ focus = true;
+ }
+
+ public void otherComponentClick() {
+ focus = false;
+ textField.setSelectionPos(textField.getCursorPosition());
+ }
+
+ public int strLenNoColor(String str) {
+ return str.replaceAll("(?i)\\u00A7.", "").length();
+ }
+
+ public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {
+ if(focus) {
+ textField.setSelectionPos(getCursorPos(mouseX, mouseY));
+ }
+ }
+
+ @Override
+ public void keyTyped(char typedChar, int keyCode) {
+ if(focus) {
+ if((options & MULTILINE) != 0) { //Carriage return
+ Pattern patternControlCode = Pattern.compile("(?i)\\u00A7([^\\u00B6\n])(?!\\u00B6)");
+
+ String text = textField.getText();
+ String textNoColour = textField.getText();
+ while(true) {
+ Matcher matcher = patternControlCode.matcher(text);
+ if(!matcher.find() || matcher.groupCount() < 1) break;
+ String code = matcher.group(1);
+ text = matcher.replaceFirst("\u00A7"+code+"\u00B6"+code);
+ }
+ while(true) {
+ Matcher matcher = patternControlCode.matcher(textNoColour);
+ if(!matcher.find() || matcher.groupCount() < 1) break;
+ String code = matcher.group(1);
+ textNoColour = matcher.replaceFirst("\u00B6"+code);
+ }
+
+ if(keyCode == 28) {
+ String before = textField.getText().substring(0, textField.getCursorPosition());
+ String after = textField.getText().substring(textField.getCursorPosition());
+ int pos = textField.getCursorPosition();
+ textField.setText(before + "\n" + after);
+ textField.setCursorPosition(pos+1);
+ return;
+ } else if(keyCode == 200) { //Up
+ String textNCBeforeCursor = textNoColour.substring(0, textField.getSelectionEnd());
+ int colorCodes = StringUtils.countMatches(textNCBeforeCursor, "\u00B6");
+ String textBeforeCursor = text.substring(0, textField.getSelectionEnd()+colorCodes*2);
+
+ int numLinesBeforeCursor = StringUtils.countMatches(textBeforeCursor, "\n");
+
+ String[] split = textBeforeCursor.split("\n");
+ int textBeforeCursorWidth;
+ String lineBefore;
+ String thisLineBeforeCursor;
+ if(split.length == numLinesBeforeCursor && split.length > 0) {
+ textBeforeCursorWidth = 0;
+ lineBefore = split[split.length-1];
+ thisLineBeforeCursor = "";
+ } else if(split.length > 1) {
+ thisLineBeforeCursor = split[split.length-1];
+ lineBefore = split[split.length-2];
+ textBeforeCursorWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(thisLineBeforeCursor);
+ } else {
+ return;
+ }
+ String trimmed = Minecraft.getMinecraft().fontRendererObj
+ .trimStringToWidth(lineBefore, textBeforeCursorWidth);
+ int linePos = strLenNoColor(trimmed);
+ if(linePos != strLenNoColor(lineBefore)) {
+ char after = lineBefore.charAt(linePos);
+ int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed);
+ int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after);
+ if(trimmedWidth + charWidth/2 < textBeforeCursorWidth) {
+ linePos++;
+ }
+ }
+ int newPos = textField.getSelectionEnd()-strLenNoColor(thisLineBeforeCursor)
+ -strLenNoColor(lineBefore)-1+linePos;
+
+ if(GuiScreen.isShiftKeyDown()) {
+ textField.setSelectionPos(newPos);
+ } else {
+ textField.setCursorPosition(newPos);
+ }
+ } else if(keyCode == 208) { //Down
+ String textNCBeforeCursor = textNoColour.substring(0, textField.getSelectionEnd());
+ int colorCodes = StringUtils.countMatches(textNCBeforeCursor, "\u00B6");
+ String textBeforeCursor = text.substring(0, textField.getSelectionEnd()+colorCodes*2);
+
+ int numLinesBeforeCursor = StringUtils.countMatches(textBeforeCursor, "\n");
+
+ String[] split = textBeforeCursor.split("\n");
+ String thisLineBeforeCursor;
+ int textBeforeCursorWidth;
+ if(split.length == numLinesBeforeCursor) {
+ thisLineBeforeCursor = "";
+ textBeforeCursorWidth = 0;
+ } else if(split.length > 0) {
+ thisLineBeforeCursor = split[split.length-1];
+ textBeforeCursorWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(thisLineBeforeCursor);
+ } else {
+ return;
+ }
+
+ String[] split2 = textNoColour.split("\n");
+ if(split2.length > numLinesBeforeCursor+1) {
+ String lineAfter = split2[numLinesBeforeCursor+1];
+ String trimmed = Minecraft.getMinecraft().fontRendererObj
+ .trimStringToWidth(lineAfter, textBeforeCursorWidth);
+ int linePos = strLenNoColor(trimmed);
+ if(linePos != strLenNoColor(lineAfter)) {
+ char after = lineAfter.charAt(linePos);
+ int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed);
+ int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after);
+ if(trimmedWidth + charWidth/2 < textBeforeCursorWidth) {
+ linePos++;
+ }
+ }
+ int newPos = textField.getSelectionEnd()-strLenNoColor(thisLineBeforeCursor)
+ +strLenNoColor(split2[numLinesBeforeCursor])+1+linePos;
+
+ if(GuiScreen.isShiftKeyDown()) {
+ textField.setSelectionPos(newPos);
+ } else {
+ textField.setCursorPosition(newPos);
+ }
+ }
+ }
+ }
+
+ String old = textField.getText();
+ if((options & FORCE_CAPS) != 0) typedChar = Character.toUpperCase(typedChar);
+ if((options & NO_SPACE) != 0 && typedChar == ' ') return;
+
+ textField.textboxKeyTyped(typedChar, keyCode);
+
+ if((options & COLOUR) != 0) {
+ if(typedChar == '&') {
+ int pos = textField.getCursorPosition()-2;
+ if(pos >= 0 && pos < textField.getText().length()) {
+ if(textField.getText().charAt(pos) == '&') {
+ String before = textField.getText().substring(0, pos);
+ String after = "";
+ if(pos+2 < textField.getText().length()) {
+ after = textField.getText().substring(pos+2);
+ }
+ textField.setText(before + "\u00A7" + after);
+ textField.setCursorPosition(pos+1);
+ }
+ }
+
+ }
+ }
+
+ if((options & NUM_ONLY) != 0 && textField.getText().matches("[^0-9]")) textField.setText(old);
+ }
+ }
+
+ public void render(int x, int y) {
+ this.x = x;
+ this.y = y;
+ drawTextbox(x, y, searchBarXSize, searchBarYSize, searchBarPadding, textField, focus);
+ }
+
+ private void drawTextbox(int x, int y, int searchBarXSize, int searchBarYSize, int searchBarPadding,
+ GuiTextField textField, boolean focus) {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+
+ GlStateManager.disableLighting();
+
+ /**
+ * Search bar
+ */
+ int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor();
+ if(paddingUnscaled < 1) paddingUnscaled = 1;
+
+ int numLines = StringUtils.countMatches(textField.getText(), "\n")+1;
+ int extraSize = (searchBarYSize-8)/2+8;
+ int bottomTextBox = y + searchBarYSize + extraSize*(numLines-1);
+
+ int borderColour = focus ? Color.GREEN.getRGB() : Color.WHITE.getRGB();
+ if(customBorderColour != -1) {
+ borderColour = customBorderColour;
+ }
+ //bar background
+ drawRect(x - paddingUnscaled,
+ y - paddingUnscaled,
+ x + searchBarXSize + paddingUnscaled,
+ bottomTextBox + paddingUnscaled, borderColour);
+ drawRect(x,
+ y,
+ x + searchBarXSize,
+ bottomTextBox, Color.BLACK.getRGB());
+
+ //bar text
+ Pattern patternControlCode = Pattern.compile("(?i)\\u00A7([^\\u00B6\n])(?!\\u00B6)");
+
+ String text = textField.getText();
+ String textNoColor = textField.getText();
+ while(true) {
+ Matcher matcher = patternControlCode.matcher(text);
+ if(!matcher.find() || matcher.groupCount() < 1) break;
+ String code = matcher.group(1);
+ text = matcher.replaceFirst("\u00A7"+code+"\u00B6"+code);
+ }
+ while(true) {
+ Matcher matcher = patternControlCode.matcher(textNoColor);
+ if(!matcher.find() || matcher.groupCount() < 1) break;
+ String code = matcher.group(1);
+ textNoColor = matcher.replaceFirst("\u00B6"+code);
+ }
+
+ String[] texts = text.split("\n");
+ for(int yOffI = 0; yOffI < texts.length; yOffI++) {
+ int yOff = yOffI*extraSize;
+
+ Minecraft.getMinecraft().fontRendererObj.drawString(Utils.trimToWidth(texts[yOffI], searchBarXSize-10), x + 5,
+ y+(searchBarYSize-8)/2+yOff, Color.WHITE.getRGB());
+ }
+
+ if(focus && System.currentTimeMillis()%1000>500) {
+ String textNCBeforeCursor = textNoColor.substring(0, textField.getCursorPosition());
+ int colorCodes = StringUtils.countMatches(textNCBeforeCursor, "\u00B6");
+ String textBeforeCursor = text.substring(0, textField.getCursorPosition()+colorCodes*2);
+
+ int numLinesBeforeCursor = StringUtils.countMatches(textBeforeCursor, "\n");
+ int yOff = numLinesBeforeCursor*extraSize;
+
+ String[] split = textBeforeCursor.split("\n");
+ int textBeforeCursorWidth;
+ if(split.length <= numLinesBeforeCursor || split.length == 0) {
+ textBeforeCursorWidth = 0;
+ } else {
+ textBeforeCursorWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(split[split.length-1]);
+ }
+ drawRect(x + 5 + textBeforeCursorWidth,
+ y+(searchBarYSize-8)/2-1 + yOff,
+ x + 5 + textBeforeCursorWidth+1,
+ y+(searchBarYSize-8)/2+9 + yOff, Color.WHITE.getRGB());
+ }
+
+ String selectedText = textField.getSelectedText();
+ if(!selectedText.isEmpty()) {
+ int leftIndex = textField.getCursorPosition() < textField.getSelectionEnd() ?
+ textField.getCursorPosition() : textField.getSelectionEnd();
+ int rightIndex = textField.getCursorPosition() > textField.getSelectionEnd() ?
+ textField.getCursorPosition() : textField.getSelectionEnd();
+
+ int texX = 0;
+ int texY = 0;
+ boolean sectionSignPrev = false;
+ boolean bold = false;
+ for(int i=0; i<textNoColor.length(); i++) {
+ char c = textNoColor.charAt(i);
+ if(sectionSignPrev) {
+ if(c != 'k' && c != 'K'
+ && c != 'm' && c != 'M'
+ && c != 'n' && c != 'N'
+ && c != 'o' && c != 'O') {
+ bold = c == 'l' || c == 'L';
+ }
+ }
+ sectionSignPrev = false;
+ if(c == '\u00B6') sectionSignPrev = true;
+
+ if(c == '\n') {
+ if(i >= leftIndex && i < rightIndex) {
+ drawRect(x + 5 + texX,
+ y+(searchBarYSize-8)/2-1 + texY,
+ x + 5 + texX + 3,
+ y+(searchBarYSize-8)/2+9 + texY, Color.LIGHT_GRAY.getRGB());
+ }
+
+ texX = 0;
+ texY += extraSize;
+ continue;
+ }
+
+ //String c2 = bold ? EnumChatFormatting.BOLD.toString() : "" + c;
+
+ int len = Minecraft.getMinecraft().fontRendererObj.getStringWidth(String.valueOf(c));
+ if(bold) len++;
+ if(i >= leftIndex && i < rightIndex) {
+ drawRect(x + 5 + texX,
+ y+(searchBarYSize-8)/2-1 + texY,
+ x + 5 + texX + len,
+ y+(searchBarYSize-8)/2+9 + texY, Color.LIGHT_GRAY.getRGB());
+
+ Minecraft.getMinecraft().fontRendererObj.drawString(String.valueOf(c),
+ x + 5 + texX,
+ y+(searchBarYSize-8)/2 + texY, Color.BLACK.getRGB());
+ if(bold) {
+ Minecraft.getMinecraft().fontRendererObj.drawString(String.valueOf(c),
+ x + 5 + texX +1,
+ y+(searchBarYSize-8)/2 + texY, Color.BLACK.getRGB());
+ }
+ }
+
+ texX += len;
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java
new file mode 100644
index 00000000..5591fcb9
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java
@@ -0,0 +1,429 @@
+package io.github.moulberry.notenoughupdates.itemeditor;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import io.github.moulberry.notenoughupdates.util.LerpingInteger;
+import io.github.moulberry.notenoughupdates.NEUManager;
+import io.github.moulberry.notenoughupdates.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.*;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.RenderHelper;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.*;
+import net.minecraft.util.ResourceLocation;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.io.IOException;
+import java.util.*;
+import java.util.List;
+import java.util.function.Supplier;
+import static io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField.*;
+
+public class NEUItemEditor extends GuiScreen {
+
+ private NEUManager manager;
+
+ private List<GuiElement> options = new ArrayList<>();
+ private List<GuiElement> rightOptions = new ArrayList<>();
+
+ private JsonObject item;
+
+ private static final int PADDING = 10;
+ private static final int SCROLL_AMOUNT = 20;
+
+ private LerpingInteger scrollHeight = new LerpingInteger(0);
+
+ private Supplier<String> internalname;
+ private Supplier<String> itemid;
+ private Supplier<String> displayname;
+ private Supplier<String> lore;
+ private Supplier<String> crafttext;
+ private Supplier<String> infoType;
+ private Supplier<String> info;
+ private Supplier<String> clickcommand;
+ private Supplier<String> damage;
+ private NBTTagCompound nbttag;
+
+ public NEUItemEditor(NEUManager manager, String internalname, JsonObject item) {
+ this.manager = manager;
+ this.item = item;
+
+ if(item.has("nbttag")) {
+ try {
+ nbttag = JsonToNBT.getTagFromJson(item.get("nbttag").getAsString());
+ } catch(NBTException e) {}
+ }
+
+ internalname = internalname == null ? "" : internalname;
+ options.add(new GuiElementText("Internal Name: ", Color.WHITE.getRGB()));
+ this.internalname = addTextFieldWithSupplier(internalname, NO_SPACE | FORCE_CAPS);
+
+ options.add(new GuiElementText("Item ID: ", Color.WHITE.getRGB()));
+ String itemid = item.has("itemid") ? item.get("itemid").getAsString() : "";
+ this.itemid = addTextFieldWithSupplier(itemid, NO_SPACE);
+
+ options.add(new GuiElementText("Display name: ", Color.WHITE.getRGB()));
+ String displayname = item.has("displayname") ? item.get("displayname").getAsString() : "";
+ this.displayname = addTextFieldWithSupplier(displayname, COLOUR);
+
+ options.add(new GuiElementText("Lore: ", Color.WHITE.getRGB()));
+ JsonArray lore = item.has("lore") ? item.get("lore").getAsJsonArray() : new JsonArray();
+ String[] loreA = new String[lore.size()];
+ for(int i=0; i<lore.size(); i++) loreA[i] = lore.get(i).getAsString();
+ this.lore = addTextFieldWithSupplier(String.join("\n", loreA), COLOUR | MULTILINE);
+
+ options.add(new GuiElementText("Craft text: ", Color.WHITE.getRGB()));
+ String crafttext = item.has("crafttext") ? item.get("crafttext").getAsString() : "";
+ this.crafttext = addTextFieldWithSupplier(crafttext, COLOUR);
+
+ options.add(new GuiElementText("Info type: ", Color.WHITE.getRGB()));
+ String infoType = item.has("infoType") ? item.get("infoType").getAsString() : "";
+ this.infoType = addTextFieldWithSupplier(infoType, NO_SPACE | FORCE_CAPS);
+
+ options.add(new GuiElementText("Additional information: ", Color.WHITE.getRGB()));
+ JsonArray info = item.has("info") ? item.get("info").getAsJsonArray() : new JsonArray();
+ String[] infoA = new String[info.size()];
+ for(int i=0; i<info.size(); i++) infoA[i] = info.get(i).getAsString();
+ this.info = addTextFieldWithSupplier(String.join("\n", infoA), COLOUR | MULTILINE);
+
+ options.add(new GuiElementText("Click-command (viewrecipe or viewpotion): ", Color.WHITE.getRGB()));
+ String clickcommand = item.has("clickcommand") ? item.get("clickcommand").getAsString() : "";
+ this.clickcommand = addTextFieldWithSupplier(clickcommand, NO_SPACE);
+
+ options.add(new GuiElementText("Damage: ", Color.WHITE.getRGB()));
+ String damage = item.has("damage") ? item.get("damage").getAsString() : "";
+ this.damage = addTextFieldWithSupplier(damage, NO_SPACE | NUM_ONLY);
+
+ rightOptions.add(new GuiElementButton("Close (discards changes)", Color.LIGHT_GRAY.getRGB(), () -> {
+ Minecraft.getMinecraft().displayGuiScreen(null);
+ }));
+ GuiElementButton button = new Object() { //Used to make the compiler shut the fuck up
+ GuiElementButton b = new GuiElementButton("Save to local disk", Color.GREEN.getRGB(), new Runnable() {
+ public void run() {
+ if(save()) {
+ b.setText("Save to local disk (SUCCESS)");
+ } else {
+ b.setText("Save to local disk (FAILED)");
+ }
+ }
+ });
+ }.b;
+ rightOptions.add(button);
+
+ button = new Object() { //Used to make the compiler shut the fuck up
+ GuiElementButton b = new GuiElementButton("Upload", Color.YELLOW.getRGB(), new Runnable() {
+ public void run() {
+ if(b.getText().equals("Upload")) {
+ b.setText("Confirm upload?");
+ } else {
+ if(upload()) {
+ b.setText("Uploaded");
+ } else {
+ b.setText("Upload failed.");
+ }
+ }
+ }
+ });
+ }.b;
+ rightOptions.add(button);
+
+ rightOptions.add(new GuiElementText("", Color.WHITE.getRGB()));
+
+ rightOptions.add(new GuiElementButton("Remove enchants", Color.RED.getRGB(), () -> {
+ nbttag.removeTag("ench");
+ nbttag.getCompoundTag("ExtraAttributes").removeTag("enchantments");
+ }));
+ rightOptions.add(new GuiElementButton("Add enchant glint", Color.ORANGE.getRGB(), () -> {
+ nbttag.setTag("ench", new NBTTagList());
+ }));
+
+ resetScrollToTop();
+ }
+
+ public boolean save() {
+ int damageI = 0;
+ try {
+ damageI = Integer.valueOf(damage.get());
+ } catch(NumberFormatException e) {}
+ resyncNbttag();
+ String[] infoA = info.get().trim().split("\n");
+ if(infoA.length == 0 || infoA[0].isEmpty()) {
+ infoA = new String[0];
+ }
+ return manager.writeItemJson(item, internalname.get(), itemid.get(), displayname.get(), lore.get().split("\n"),
+ crafttext.get(), infoType.get(), infoA, clickcommand.get(), damageI, nbttag);
+ }
+
+ public boolean upload() {
+ int damageI = 0;
+ try {
+ damageI = Integer.valueOf(damage.get());
+ } catch(NumberFormatException e) {}
+ resyncNbttag();
+ String[] infoA = info.get().trim().split("\n");
+ if(infoA.length == 0 || infoA[0].isEmpty()) {
+ infoA = new String[0];
+ }
+ return manager.uploadItemJson(internalname.get(), itemid.get(), displayname.get(), lore.get().split("\n"),
+ crafttext.get(), infoType.get(), infoA, clickcommand.get(), damageI, nbttag);
+ }
+
+ public void onGuiClosed() {
+ Keyboard.enableRepeatEvents(false);
+ }
+
+ public Supplier<String> addTextFieldWithSupplier(String initialText, int options) {
+ GuiElementTextField textField = new GuiElementTextField(initialText, options);
+ this.options.add(textField);
+ return () -> textField.toString();
+ }
+
+ public void resyncNbttag() {
+ if(nbttag == null) nbttag = new NBTTagCompound();
+
+ //Item lore
+ NBTTagList list = new NBTTagList();
+ for(String lore : this.lore.get().split("\n")) {
+ list.appendTag(new NBTTagString(lore));
+ }
+
+ NBTTagCompound display = nbttag.getCompoundTag("display");
+ display.setTag("Lore", list);
+
+ //Name
+ display.setString("Name", displayname.get());
+ nbttag.setTag("display", display);
+
+ //Internal ID
+ NBTTagCompound ea = nbttag.getCompoundTag("ExtraAttributes");
+ ea.setString("id", internalname.get());
+ nbttag.setTag("ExtraAttributes", ea);
+ }
+
+ public void resetScrollToTop() {
+ int totalHeight = PADDING;
+ for(GuiElement gui : options) {
+ totalHeight += gui.getHeight();
+ }
+
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int height = scaledresolution.getScaledHeight();
+
+ scrollHeight.setValue(totalHeight-height+PADDING);
+ }
+
+ public int calculateYScroll() {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int height = scaledresolution.getScaledHeight();
+
+ int totalHeight = PADDING;
+ for(GuiElement gui : options) {
+ totalHeight += gui.getHeight();
+ }
+
+ if(scrollHeight.getValue() < 0) scrollHeight.setValue(0);
+
+ int yScroll = 0;
+ if(totalHeight > height-PADDING) {
+ yScroll = totalHeight-height+PADDING-scrollHeight.getValue();
+ } else {
+ scrollHeight.setValue(0);
+ }
+ if(yScroll < 0) {
+ yScroll = 0;
+ scrollHeight.setValue(totalHeight-height+PADDING);
+ }
+
+ return yScroll;
+ }
+
+ @Override
+ public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+ scrollHeight.tick();
+
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int width = scaledresolution.getScaledWidth();
+ int height = scaledresolution.getScaledHeight();
+
+ GlStateManager.disableLighting();
+
+ Color backgroundColour = new Color(10, 10, 10, 240);
+ drawRect(0, 0, width, height, backgroundColour.getRGB());
+
+ int yScroll = calculateYScroll();
+ if(yScroll > 0){
+ //Render scroll bar
+ }
+
+ int currentY = PADDING-yScroll;
+ for(GuiElement gui : options) {
+ gui.render(PADDING, currentY);
+ currentY += gui.getHeight();
+ }
+
+ currentY = PADDING;
+ for(GuiElement gui : rightOptions) {
+ gui.render(width-PADDING-gui.getWidth(), currentY);
+ currentY += gui.getHeight();
+ }
+
+ int itemX = 424;
+ int itemY = 32;
+ int itemSize = 128;
+ Color itemBorder = new Color(100, 50, 150, 255);
+ Color itemBackground = new Color(120, 120, 120, 255);
+ drawRect(itemX-10, itemY-10, itemX+itemSize+10, itemY+itemSize+10, Color.DARK_GRAY.getRGB());
+ drawRect(itemX-9, itemY-9, itemX+itemSize+9, itemY+itemSize+9, itemBorder.getRGB());
+ drawRect(itemX-6, itemY-6, itemX+itemSize+6, itemY+itemSize+6, Color.DARK_GRAY.getRGB());
+ drawRect(itemX-5, itemY-5, itemX+itemSize+5, itemY+itemSize+5, itemBackground.getRGB());
+ ItemStack stack = new ItemStack(Item.itemRegistry.getObject(new ResourceLocation(itemid.get())));
+
+ if(stack.getItem() != null) {
+ try {
+ stack.setItemDamage(Integer.valueOf(damage.get()));
+ } catch(NumberFormatException e) {}
+
+ resyncNbttag();
+ stack.setTagCompound(nbttag);
+
+ int scaleFactor = itemSize/16;
+ GL11.glPushMatrix();
+ GlStateManager.scale(scaleFactor, scaleFactor, 1);
+ drawItemStack(stack, itemX/scaleFactor, itemY/scaleFactor, null);
+ GL11.glPopMatrix();
+ }
+
+ //Tooltip
+ List<String> text = new ArrayList<>();
+ text.add(displayname.get());
+ text.addAll(Arrays.asList(lore.get().split("\n")));
+
+ Utils.drawHoveringText(text, itemX-20, itemY+itemSize+28, width, height, -1,
+ Minecraft.getMinecraft().fontRendererObj);
+
+ GlStateManager.disableLighting();
+ }
+
+ @Override
+ protected void keyTyped(char typedChar, int keyCode) {
+ for(GuiElement gui : options) {
+ gui.keyTyped(typedChar, keyCode);
+ }
+ }
+
+ @Override
+ protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int width = scaledresolution.getScaledWidth();
+
+ int yScroll = calculateYScroll();
+ int currentY = PADDING-yScroll;
+ for(GuiElement gui : options) {
+ if(mouseY > currentY && mouseY < currentY+gui.getHeight()
+ && mouseX < gui.getWidth()) {
+ gui.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick);
+ return;
+ }
+ currentY += gui.getHeight();
+ }
+
+ currentY = PADDING;
+ for(GuiElement gui : rightOptions) {
+ if(mouseY > currentY && mouseY < currentY+gui.getHeight()
+ && mouseX > width-PADDING-gui.getWidth()) {
+ gui.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick);
+ return;
+ }
+ currentY += gui.getHeight();
+ }
+ }
+
+ @Override
+ public void handleMouseInput() throws IOException {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+
+ int maxWidth = 0;
+ for(GuiElement gui : options) {
+ if(gui.getWidth() > maxWidth) maxWidth = gui.getWidth();
+ }
+
+ if(Mouse.getX() < maxWidth*scaledresolution.getScaleFactor()) {
+ int dWheel = Mouse.getEventDWheel();
+
+ if(dWheel < 0) {
+ scrollHeight.setTarget(scrollHeight.getTarget()-SCROLL_AMOUNT);
+ scrollHeight.resetTimer();
+ } else if(dWheel > 0) {
+ scrollHeight.setTarget(scrollHeight.getTarget()+SCROLL_AMOUNT);
+ scrollHeight.resetTimer();
+ }
+ }
+
+ super.handleMouseInput();
+ }
+
+ @Override
+ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
+ ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+ int width = scaledresolution.getScaledWidth();
+
+ int yScroll = calculateYScroll();
+ int currentY = PADDING-yScroll;
+ for(GuiElement gui : options) {
+ if(mouseY > currentY && mouseY < currentY+gui.getHeight()
+ && mouseX < gui.getWidth()) {
+ gui.mouseClicked(mouseX, mouseY, mouseButton);
+ for(GuiElement gui2 : options) {
+ if(gui2 != gui) {
+ gui2.otherComponentClick();
+ }
+ }
+ for(GuiElement gui2 : rightOptions) {
+ if(gui2 != gui) {
+ gui2.otherComponentClick();
+ }
+ }
+ return;
+ }
+ currentY += gui.getHeight();
+ }
+
+ currentY = PADDING;
+ for(GuiElement gui : rightOptions) {
+ if(mouseY > currentY && mouseY < currentY+gui.getHeight()
+ && mouseX > width-PADDING-gui.getWidth()) {
+ gui.mouseClicked(mouseX, mouseY, mouseButton);
+ for(GuiElement gui2 : options) {
+ if(gui2 != gui) {
+ gui2.otherComponentClick();
+ }
+ }
+ for(GuiElement gui2 : rightOptions) {
+ if(gui2 != gui) {
+ gui2.otherComponentClick();
+ }
+ }
+ return;
+ }
+ currentY += gui.getHeight();
+ }
+ }
+
+ private void drawItemStack(ItemStack stack, int x, int y, String altText) {
+ RenderItem itemRender = Minecraft.getMinecraft().getRenderItem();
+ FontRenderer font = Minecraft.getMinecraft().fontRendererObj;
+
+ RenderHelper.enableGUIStandardItemLighting();
+ itemRender.renderItemAndEffectIntoGUI(stack, x, y);
+ RenderHelper.disableStandardItemLighting();
+
+ itemRender.renderItemOverlayIntoGUI(font, stack, x, y, altText);
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java
new file mode 100644
index 00000000..4a713218
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java
@@ -0,0 +1,20 @@
+package io.github.moulberry.notenoughupdates.mixins;
+
+import io.github.moulberry.notenoughupdates.Utils;
+import net.minecraft.item.ItemStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin({ItemStack.class})
+public class MixinItemStack {
+
+ @Inject(method="hasEffect", at=@At("HEAD"), cancellable = true)
+ public void hasEffect(CallbackInfoReturnable cir) {
+ if(Utils.getHasEffectOverride()) {
+ cir.setReturnValue(false);
+ }
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/Options.java b/src/main/java/io/github/moulberry/notenoughupdates/options/Options.java
new file mode 100644
index 00000000..6c7a70a0
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/options/Options.java
@@ -0,0 +1,203 @@
+package io.github.moulberry.notenoughupdates.options;
+
+import com.google.common.collect.Lists;
+import com.google.gson.*;
+import com.google.gson.annotations.Expose;
+import io.github.moulberry.notenoughupdates.Utils;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Options {
+
+ public Option<Boolean> enableItemEditing = new Option(
+ false,
+ "Enable Item Editing",
+ true,
+ "Dev Feature. Please don't use.");
+ public Option<Boolean> onlyShowOnSkyblock = new Option(
+ true,
+ "Only Show On Skyblock",
+ false,
+ "GUI Overlay only appears when you are playing Skyblock.");
+ public Option<Boolean> advancedPriceInfo = new Option(
+ false,
+ "Advanced Price Information",
+ false,
+ "Shows some extra information about item sales.");
+ public Option<Boolean> cacheRenderedItempane = new Option(
+ true,
+ "Cache Rendered Itempane",
+ false,
+ "Caches the drawn itempane, drastically improving performance. However, animated textures will not work.");
+ public Option<Double> bgBlurFactor = new Option(
+ 5.0,
+ "Background Blur Factor",
+ false,
+ "Changes the strength of pane background blur. 0-50.");
+ public Option<String> apiKey = new Option(
+ "",
+ "Api key used for certain features.",
+ false,
+ "Type /api new to receive key and put it here.");
+ public Option<Boolean> autoupdate = new Option(
+ true,
+ "Automatically Update Items",
+ false,
+ "If true, updated items will automatically download from the remote repository when you start the game. \nHIGHLY RECOMMENDED.");
+ public Option<Boolean> keepopen = new Option(
+ false,
+ "Keep Itempane Open",
+ false,
+ "If true, the itempane will stay open after the gui is closed.");
+ public Option<Boolean> itemStyle = new Option(
+ true,
+ "Circular Item BG Style",
+ false,
+ "If true, uses the circular item background style instead of the square style.");
+ public Option<Double> paneWidthMult = new Option(
+ 1.0,
+ "Pane Width Multiplier",
+ false,
+ "Changes how wide the item and info panes are. Value between 0.5-1.5.");
+ public Option<Double> bgOpacity = new Option(
+ 50.0,
+ "Pane Background Opacity",
+ false,
+ "Changes the background colour opacity of item and info panes. Value between 0-255.");
+ public Option<Double> fgOpacity = new Option(
+ 255.0,
+ "Item Background Opacity",
+ false,
+ "Changes the opacity of item background. Value between 0-255.");
+
+ /**
+ * OPTIONS THAT DON'T SHOW IN GUI
+ */
+ public Option<Boolean> dev = new Option(
+ false,
+ "Show Dev Options",
+ true,
+ "Dev Feature. Please don't use.");
+ public Option<Double> compareMode = new Option(
+ 0.0,
+ "Compare Mode",
+ false,
+ "Compare Mode");
+ public Option<Double> sortMode = new Option(
+ 0.0,
+ "Sort Mode",
+ false,
+ "Sort Mode");
+ public Option<ArrayList<Boolean>> compareAscending = new Option(
+ Utils.createList(true, true),
+ "Compare Ascending",
+ false,
+ "Compare Ascending");
+ public Option<ArrayList<String>> favourites = new Option(
+ new ArrayList<String>(),
+ "Favourites",
+ false,
+ "Favourites");
+
+ public List<Option> getOptions() {
+ List<Option> options = new ArrayList<>();
+
+ tryAddOption(enableItemEditing, options);
+ tryAddOption(onlyShowOnSkyblock, options);
+ tryAddOption(advancedPriceInfo, options);
+ tryAddOption(cacheRenderedItempane, options);
+ tryAddOption(autoupdate, options);
+ tryAddOption(keepopen, options);
+ tryAddOption(itemStyle, options);
+ tryAddOption(bgBlurFactor, options);
+ tryAddOption(apiKey, options);
+ tryAddOption(paneWidthMult, options);
+ tryAddOption(bgOpacity, options);
+ tryAddOption(fgOpacity, options);
+
+ return options;
+ }
+
+ private void tryAddOption(Option<?> option, List<Option> list) {
+ if(!option.secret || dev.value) {
+ list.add(option);
+ }
+ }
+
+ public static class Option<T> implements Serializable {
+ public T value;
+ public final transient T defaultValue;
+ public final transient String displayName;
+ public final transient boolean secret;
+ public final transient String desc;
+
+ public Option(T defaultValue, String displayName, boolean secret, String desc) {
+ this.value = defaultValue;
+ this.defaultValue = defaultValue;
+ this.displayName = displayName;
+ this.secret = secret;
+ this.desc = desc;
+ }
+
+ public void setValue(String value) {
+ if(this.value instanceof Boolean) {
+ ((Option<Boolean>) this).value = Boolean.valueOf(value);
+ } else if(this.value instanceof Double) {
+ ((Option<Double>)this).value = Double.valueOf(value);
+ } else if(this.value instanceof String) {
+ ((Option<String>)this).value = value;
+ }
+ }
+ }
+
+ public static JsonSerializer<Option<?>> createSerializer() {
+ return (src, typeOfSrc, context) -> {
+ if(src.secret && src.defaultValue.equals(src.value)) {
+ return null;
+ }
+ return context.serialize(src.value);
+ };
+ }
+
+ public static JsonDeserializer<Option<?>> createDeserializer() {
+ return (json, typeOfT, context) -> {
+ try {
+ return new Option(context.deserialize(json, Object.class), "unknown", false, "unknown");
+ } catch(Exception e) {
+ return null;
+ }
+ };
+ }
+
+ public static Options loadFromFile(Gson gson, File file) throws IOException {
+ InputStream in = new FileInputStream(file);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+
+ Options oLoad = gson.fromJson(reader, Options.class);
+ Options oDefault = new Options();
+ if(oLoad == null) return oDefault;
+
+ for(Field f : Options.class.getDeclaredFields()) {
+ try {
+ ((Option)f.get(oDefault)).value = ((Option)f.get(oLoad)).value;
+ } catch (Exception e) { }
+ }
+ return oDefault;
+ }
+
+ public void saveToFile(Gson gson, File file) throws IOException {
+ file.createNewFile();
+
+ try(BufferedWriter writer = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
+ writer.write(gson.toJson(this));
+ }
+ }
+
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/HTMLParagraphView.java b/src/main/java/io/github/moulberry/notenoughupdates/util/HTMLParagraphView.java
new file mode 100644
index 00000000..ab4be4a7
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/HTMLParagraphView.java
@@ -0,0 +1,30 @@
+package io.github.moulberry.notenoughupdates.util;
+
+import javax.swing.text.Element;
+import javax.swing.text.FlowView;
+import javax.swing.text.View;
+import javax.swing.text.html.ParagraphView;
+
+class HTMLParagraphView extends ParagraphView {
+
+ public static int MAX_VIEW_SIZE=100;
+
+ public HTMLParagraphView(Element elem) {
+ super(elem);
+ strategy = new HTMLParagraphView.HTMLFlowStrategy();
+ }
+
+ public static class HTMLFlowStrategy extends FlowStrategy {
+ protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) {
+ View res=super.createView(fv, startOffset, spanLeft, rowIndex);
+ if (res.getEndOffset()-res.getStartOffset()> MAX_VIEW_SIZE) {
+ res = res.createFragment(startOffset, startOffset+ MAX_VIEW_SIZE);
+ }
+ return res;
+ }
+
+ }
+ public int getResizeWeight(int axis) {
+ return 0;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/HtmlImageGenerator.java b/src/main/java/io/github/moulberry/notenoughupdates/util/HtmlImageGenerator.java
new file mode 100644
index 00000000..40866a00
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/HtmlImageGenerator.java
@@ -0,0 +1,125 @@
+//
+// Source code recreated from a .class file by IntelliJ IDEA
+// (powered by Fernflower decompiler)
+//
+package io.github.moulberry.notenoughupdates.util;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import javax.imageio.ImageIO;
+import javax.swing.JEditorPane;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.ImageView;
+import javax.swing.text.html.StyleSheet;
+
+public class HtmlImageGenerator {
+ public JEditorPane editorPane = this.createJEditorPane();
+ static final Dimension DEFAULT_SIZE = new Dimension(800, 800);
+
+ public HtmlImageGenerator() {
+ }
+
+ public ComponentOrientation getOrientation() {
+ return this.editorPane.getComponentOrientation();
+ }
+
+ public void setOrientation(ComponentOrientation orientation) {
+ this.editorPane.setComponentOrientation(orientation);
+ }
+
+ public Dimension getSize() {
+ return this.editorPane.getSize();
+ }
+
+ public void setSize(Dimension dimension) {
+ this.editorPane.setSize(dimension);
+ }
+
+ public void loadUrl(URL url) {
+ try {
+ this.editorPane.setPage(url);
+ } catch (IOException var3) {
+ throw new RuntimeException(String.format("Exception while loading %s", url), var3);
+ }
+ }
+
+ public void loadUrl(String url) {
+ try {
+ this.editorPane.setPage(url);
+ } catch (IOException var3) {
+ throw new RuntimeException(String.format("Exception while loading %s", url), var3);
+ }
+ }
+
+ public void loadHtml(String html) {
+ this.editorPane.setText(html);
+ this.onDocumentLoad();
+ }
+
+ public void saveAsImage(String file) {
+ this.saveAsImage(new File(file));
+ }
+
+ public void saveAsImage(File file) {
+ BufferedImage img = this.getBufferedImage();
+
+ try {
+ ImageIO.write(img, "png", file);
+ } catch (IOException var4) {
+ throw new RuntimeException(String.format("Exception while saving '%s' image", file), var4);
+ }
+ }
+
+ protected void onDocumentLoad() {
+ }
+
+ public Dimension getDefaultSize() {
+ return DEFAULT_SIZE;
+ }
+
+ public BufferedImage getBufferedImage() {
+ Dimension prefSize = this.editorPane.getPreferredSize();
+ BufferedImage img = new BufferedImage(prefSize.width, this.editorPane.getPreferredSize().height, 2);
+ Graphics graphics = img.getGraphics();
+ this.editorPane.setSize(prefSize);
+ this.editorPane.paint(graphics);
+ return img;
+ }
+
+ public void addCss(String css) {
+ HTMLEditorKit kit = (HTMLEditorKit) editorPane.getEditorKitForContentType("text/html");
+ kit.getStyleSheet().addRule(css);
+ }
+
+ public void setScale(float factor) {
+ editorPane.getDocument().putProperty("ZOOM_FACTOR", new Double(factor));
+ }
+
+ protected JEditorPane createJEditorPane() {
+ JEditorPane editorPane = new JEditorPane();
+ editorPane.setSize(this.getDefaultSize());
+ editorPane.setEditable(false);
+ HTMLEditorKit kit = new LargeHTMLEditorKit();
+ editorPane.setEditorKitForContentType("text/html", kit);
+ editorPane.setBackground(new Color(0, true));
+ editorPane.setContentType("text/html");
+ editorPane.addPropertyChangeListener(new PropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (evt.getPropertyName().equals("page")) {
+ HtmlImageGenerator.this.onDocumentLoad();
+ }
+
+ }
+ });
+ return editorPane;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java b/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java
new file mode 100644
index 00000000..a39da281
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java
@@ -0,0 +1,68 @@
+package io.github.moulberry.notenoughupdates.util;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Consumer;
+
+public class HypixelApi {
+
+ private Gson gson = new Gson();
+ private ExecutorService es = Executors.newSingleThreadExecutor();
+
+ //https://api.hypixel.net/skyblock/profiles?key=06b20aa2-ff04-4851-9687-669c8111526f&uuid=d0e05de7-6067-454d-beae-c6d19d886191
+ public void getHypixelApiAsync(String apiKey, String method, HashMap<String, String> args, Consumer<JsonObject> consumer) {
+ getHypixelApiAsync(generateApiUrl(apiKey, method, args), consumer);
+ }
+
+ public void getHypixelApiAsync(String urlS, Consumer<JsonObject> consumer) {
+ es.submit(() -> {
+ consumer.accept(getHypixelApiSync(urlS));
+ });
+ }
+
+ public JsonObject getHypixelApiSync(String urlS) {
+ URLConnection connection;
+ try {
+ URL url = new URL(urlS);
+ connection = url.openConnection();
+ connection.setConnectTimeout(3000);
+ } catch(IOException e) {
+ return null;
+ }
+
+ try(BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+ StringBuilder builder = new StringBuilder();
+ int codePoint;
+ while((codePoint = reader.read()) != -1) {
+ builder.append(((char)codePoint));
+ }
+ String response = builder.toString();
+
+ JsonObject json = gson.fromJson(response, JsonObject.class);
+ return json;
+ } catch(IOException e) {
+ return null;
+ }
+ }
+
+ public String generateApiUrl(String apiKey, String method, HashMap<String, String> args) {
+ String url = "https://api.hypixel.net/" + method + "?key=" + apiKey;
+ for(Map.Entry<String, String> entry : args.entrySet()) {
+ url += "&" + entry.getKey() + "=" + entry.getValue();
+ }
+ return url;
+ }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/LargeHTMLEditorKit.java b/src/main/java/io/github/moulberry/notenoughupdates/util/LargeHTMLEditorKit.java
new file mode 100644
index 00000000..7b69a541
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/LargeHTMLEditorKit.java
@@ -0,0 +1,132 @@
+package io.github.moulberry.notenoughupdates.util;
+
+import javax.swing.text.*;
+import javax.swing.text.html.*;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+
+public class LargeHTMLEditorKit extends HTMLEditorKit {
+
+ ViewFactory factory = new MyViewFactory();
+
+ @Override
+ public ViewFactory getViewFactory() {
+ return factory;
+ }
+
+ public Document createDefaultDocument() {
+ HTMLDocument doc = (HTMLDocument)super.createDefaultDocument();
+ doc.setAsynchronousLoadPriority(-1);
+ return doc;
+ }
+
+ class MyViewFactory extends HTMLFactory {
+ @Override
+ public View create(Element elem) {
+ AttributeSet attrs = elem.getAttributes();
+ Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute);
+ Object o = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute);
+ if (o instanceof HTML.Tag) {
+ HTML.Tag kind = (HTML.Tag) o;
+ if (kind == HTML.Tag.HTML) {
+ return new HTMLBlockView(elem);
+ }
+ }
+ View view = super.create(elem);
+ if(view instanceof ImageView) {
+ //((ImageView)view).setLoadsSynchronously(true);
+ }
+ return view;
+ }
+
+ }
+
+
+ private class HTMLBlockView extends BlockView {
+
+ public HTMLBlockView(Element elem) {
+ super(elem, View.Y_AXIS);
+ }
+
+ @Override
+ protected void layout(int width, int height) {
+ if (width<Integer.MAX_VALUE) {
+ super.layout(new Double(width / getZoomFactor()).intValue(),
+ new Double(height *
+ getZoomFactor()).intValue());
+ }
+ }
+
+ public double getZoomFactor() {
+ Double scale = (Double) getDocument().getProperty("ZOOM_FACTOR");
+ if (scale != null) {
+ return scale.doubleValue();
+ }
+
+ return 1;
+ }
+
+ @Override
+ public void paint(Graphics g, Shape allocation) {
+ Graphics2D g2d = (Graphics2D) g;
+ double zoomFactor = getZoomFactor();
+ AffineTransform old = g2d.getTransform();
+ g2d.scale(zoomFactor, zoomFactor);
+ super.paint(g2d, allocation);
+ g2d.setTransform(old);
+ }
+
+ @Override
+ public float getMinimumSpan(int axis) {
+ float f = super.getMinimumSpan(axis);
+ f *= getZoomFactor();
+ return f;
+ }
+
+ @Override
+ public float getMaximumSpan(int axis) {
+ float f = super.getMaximumSpan(axis);
+ f *= getZoomFactor();
+ return f;
+ }
+
+ @Override
+ public float getPreferredSpan(int axis) {
+ float f = super.getPreferredSpan(axis);
+ f *= getZoomFactor();
+ return f;
+ }
+
+ @Override
+ public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
+ double zoomFactor = getZoomFactor();
+ Rectangle alloc;
+ alloc = a.getBounds();
+ Shape s = super.modelToView(pos, alloc, b);
+ alloc = s.getBounds();
+ alloc.x *= zoomFactor;
+ alloc.y *= zoomFactor;
+ alloc.width *= zoomFactor;
+ alloc.height *= zoomFactor;
+
+ return alloc;
+ }
+
+ @Override
+ public int viewToModel(float x, float y, Shape a,
+ Position.Bias[] bias) {
+ double zoomFactor = getZoomFactor();
+ Rectangle alloc = a.getBounds();
+ x /= zoomFactor;
+ y /= zoomFactor;
+ alloc.x /= zoomFactor;
+ alloc.y /= zoomFactor;
+ alloc.width /= zoomFactor;
+ alloc.height /= zoomFactor;
+
+ return super.viewToModel(x, y, alloc, bias);
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingFloat.java b/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingFloat.java
new file mode 100644
index 00000000..83fa1a6d
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingFloat.java
@@ -0,0 +1,68 @@
+package io.github.moulberry.notenoughupdates.util;
+
+public class LerpingFloat {
+
+ private int timeSpent;
+ private long lastMillis;
+ private int timeToReachTarget;
+
+ private float targetValue;
+ private float lerpValue;
+
+ public LerpingFloat(float initialValue, int timeToReachTarget) {
+ this.targetValue = this.lerpValue = initialValue;
+ this.timeToReachTarget = timeToReachTarget;
+ }
+
+ public LerpingFloat(int initialValue) {
+ this(initialValue, 200);
+ }
+
+ public void tick() {
+ int lastTimeSpent = timeSpent;
+ this.timeSpent += System.currentTimeMillis() - lastMillis;
+
+ float lastDistPercentToTarget = lastTimeSpent/(float)timeToReachTarget;
+ float distPercentToTarget = timeSpent/(float)timeToReachTarget;
+ float fac = (1-lastDistPercentToTarget)/lastDistPercentToTarget;
+
+ float startValue = lerpValue - (targetValue - lerpValue)/fac;
+
+ float dist = targetValue - startValue;
+ if(dist == 0) return;
+
+ float oldLerpValue = lerpValue;
+ if(distPercentToTarget >= 1) {
+ lerpValue = targetValue;
+ } else {
+ lerpValue = startValue + dist*distPercentToTarget;
+ }
+
+ if(lerpValue == oldLerpValue) {
+ timeSpent = lastTimeSpent;
+ } else {
+ this.lastMillis = System.currentTimeMillis();
+ }
+ }
+
+ public void resetTimer() {
+ this.timeSpent = 0;
+ this.lastMillis = System.currentTimeMillis();
+ }
+
+ public void setTarget(float targetValue) {
+ this.targetValue = targetValue;
+ }
+
+ public void setValue(float value) {
+ this.targetValue = this.lerpValue = value;
+ }
+
+ public float getValue() {
+ return lerpValue;
+ }
+
+ public float getTarget() {
+ return targetValue;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingInteger.java b/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingInteger.java
new file mode 100644
index 00000000..93c011e1
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingInteger.java
@@ -0,0 +1,68 @@
+package io.github.moulberry.notenoughupdates.util;
+
+public class LerpingInteger {
+
+ private int timeSpent;
+ private long lastMillis;
+ private int timeToReachTarget;
+
+ private int targetValue;
+ private int lerpValue;
+
+ public LerpingInteger(int initialValue, int timeToReachTarget) {
+ this.targetValue = this.lerpValue = initialValue;
+ this.timeToReachTarget = timeToReachTarget;
+ }
+
+ public LerpingInteger(int initialValue) {
+ this(initialValue, 200);
+ }
+
+ public void tick() {
+ int lastTimeSpent = timeSpent;
+ this.timeSpent += System.currentTimeMillis() - lastMillis;
+
+ float lastDistPercentToTarget = lastTimeSpent/(float)timeToReachTarget;
+ float distPercentToTarget = timeSpent/(float)timeToReachTarget;
+ float fac = (1-lastDistPercentToTarget)/lastDistPercentToTarget;
+
+ int startValue = lerpValue - (int)((targetValue - lerpValue)/fac);
+
+ int dist = targetValue - startValue;
+ if(dist == 0) return;
+
+ int oldLerpValue = lerpValue;
+ if(distPercentToTarget >= 1) {
+ lerpValue = targetValue;
+ } else {
+ lerpValue = startValue + (int)(dist*distPercentToTarget);
+ }
+
+ if(lerpValue == oldLerpValue) {
+ timeSpent = lastTimeSpent;
+ } else {
+ this.lastMillis = System.currentTimeMillis();
+ }
+ }
+
+ public void resetTimer() {
+ this.timeSpent = 0;
+ this.lastMillis = System.currentTimeMillis();
+ }
+
+ public void setTarget(int targetValue) {
+ this.targetValue = targetValue;
+ }
+
+ public void setValue(int value) {
+ this.targetValue = this.lerpValue = value;
+ }
+
+ public int getValue() {
+ return lerpValue;
+ }
+
+ public int getTarget() {
+ return targetValue;
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/SynchronousHTMLEditorKit.java b/src/main/java/io/github/moulberry/notenoughupdates/util/SynchronousHTMLEditorKit.java
new file mode 100644
index 00000000..7f02baa7
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/SynchronousHTMLEditorKit.java
@@ -0,0 +1,36 @@
+package io.github.moulberry.notenoughupdates.util;
+
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.View;
+import javax.swing.text.ViewFactory;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.ImageView;
+import javax.swing.text.html.HTMLEditorKit.HTMLFactory;
+
+public class SynchronousHTMLEditorKit extends HTMLEditorKit {
+ public SynchronousHTMLEditorKit() {
+ }
+
+ public Document createDefaultDocument() {
+ HTMLDocument doc = (HTMLDocument)super.createDefaultDocument();
+ doc.setAsynchronousLoadPriority(-1);
+ return doc;
+ }
+
+ public ViewFactory getViewFactory() {
+ return new HTMLFactory() {
+ public View create(Element elem) {
+ View view = super.create(elem);
+ if (view instanceof ImageView) {
+ //((ImageView)view).setLoadsSynchronously(true);
+ }
+
+ return view;
+ }
+ };
+ }
+
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/TexLoc.java b/src/main/java/io/github/moulberry/notenoughupdates/util/TexLoc.java
new file mode 100644
index 00000000..47a9c86b
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/TexLoc.java
@@ -0,0 +1,50 @@
+package io.github.moulberry.notenoughupdates.util;
+
+import org.lwjgl.input.Keyboard;
+
+public class TexLoc {
+
+ public int x;
+ public int y;
+ private int toggleKey;
+ private boolean toggled;
+ private boolean pressedLastTick;
+ private boolean dirPressed;
+
+ public TexLoc(int x, int y, int toggleKey) {
+ this.x = x;
+ this.y = y;
+ this.toggleKey = toggleKey;
+ }
+
+ public void handleKeyboardInput() {
+ if(Keyboard.isKeyDown(toggleKey)) {
+ if(!pressedLastTick) {
+ toggled = !toggled;
+ }
+ pressedLastTick = true;
+ } else {
+ pressedLastTick = false;
+ }
+ if(toggled) {
+ if(Keyboard.isKeyDown(Keyboard.KEY_LEFT)) {
+ if(!dirPressed) x--;
+ dirPressed = true;
+ } else if(Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) {
+ if(!dirPressed) x++;
+ dirPressed = true;
+ } else if(Keyboard.isKeyDown(Keyboard.KEY_UP)) {
+ if(!dirPressed) y--;
+ dirPressed = true;
+ } else if(Keyboard.isKeyDown(Keyboard.KEY_DOWN)) {
+ if(!dirPressed) y++;
+ dirPressed = true;
+ } else {
+ dirPressed = false;
+ return;
+ }
+ System.out.println("X: " + x + " ; Y: " + y);
+ }
+ }
+
+}
diff --git a/src/main/resources/assets/notenoughupdates/ascending_overlay.png b/src/main/resources/assets/notenoughupdates/ascending_overlay.png
new file mode 100644
index 00000000..5b703a93
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/ascending_overlay.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/close.png b/src/main/resources/assets/notenoughupdates/close.png
new file mode 100644
index 00000000..8ba630fb
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/close.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/descending_overlay.png b/src/main/resources/assets/notenoughupdates/descending_overlay.png
new file mode 100644
index 00000000..ad9d0bd6
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/descending_overlay.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/help.png b/src/main/resources/assets/notenoughupdates/help.png
new file mode 100644
index 00000000..1a7ebb60
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/help.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/item_edit.png b/src/main/resources/assets/notenoughupdates/item_edit.png
new file mode 100644
index 00000000..6ab71631
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/item_edit.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/item_mask.png b/src/main/resources/assets/notenoughupdates/item_mask.png
new file mode 100644
index 00000000..7a202a33
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/item_mask.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/item_pane_tab_arrow.png b/src/main/resources/assets/notenoughupdates/item_pane_tab_arrow.png
new file mode 100644
index 00000000..d8fc8530
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/item_pane_tab_arrow.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/logo.png b/src/main/resources/assets/notenoughupdates/logo.png
new file mode 100644
index 00000000..c08a8991
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/logo.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/logo_bg.png b/src/main/resources/assets/notenoughupdates/logo_bg.png
new file mode 100644
index 00000000..188c3126
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/logo_bg.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/logo_fg.png b/src/main/resources/assets/notenoughupdates/logo_fg.png
new file mode 100644
index 00000000..1f170cbc
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/logo_fg.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/next.png b/src/main/resources/assets/notenoughupdates/next.png
new file mode 100644
index 00000000..d215a7e8
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/next.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/next.xcf b/src/main/resources/assets/notenoughupdates/next.xcf
new file mode 100644
index 00000000..f93cc5b9
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/next.xcf
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/off.png b/src/main/resources/assets/notenoughupdates/off.png
new file mode 100644
index 00000000..174d1b4f
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/off.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/on.png b/src/main/resources/assets/notenoughupdates/on.png
new file mode 100644
index 00000000..5520a661
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/on.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/order_alphabetical.png b/src/main/resources/assets/notenoughupdates/order_alphabetical.png
new file mode 100644
index 00000000..4e204691
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/order_alphabetical.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/order_alphabetical_active.png b/src/main/resources/assets/notenoughupdates/order_alphabetical_active.png
new file mode 100644
index 00000000..ab596112
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/order_alphabetical_active.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/order_rarity.png b/src/main/resources/assets/notenoughupdates/order_rarity.png
new file mode 100644
index 00000000..df23e5da
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/order_rarity.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/order_rarity_active.png b/src/main/resources/assets/notenoughupdates/order_rarity_active.png
new file mode 100644
index 00000000..84bd3318
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/order_rarity_active.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/prev.png b/src/main/resources/assets/notenoughupdates/prev.png
new file mode 100644
index 00000000..a4ec7743
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/prev.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/prev.xcf b/src/main/resources/assets/notenoughupdates/prev.xcf
new file mode 100644
index 00000000..e9fecc61
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/prev.xcf
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/prev_unhovered.png b/src/main/resources/assets/notenoughupdates/prev_unhovered.png
new file mode 100644
index 00000000..f4340fee
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/prev_unhovered.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/rightarrow.png b/src/main/resources/assets/notenoughupdates/rightarrow.png
new file mode 100644
index 00000000..013fe8b9
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/rightarrow.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/rightarrow_overlay.png b/src/main/resources/assets/notenoughupdates/rightarrow_overlay.png
new file mode 100644
index 00000000..cb91ada4
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/rightarrow_overlay.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/settings.png b/src/main/resources/assets/notenoughupdates/settings.png
new file mode 100644
index 00000000..481a0588
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/settings.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_accessory.png b/src/main/resources/assets/notenoughupdates/sort_accessory.png
new file mode 100644
index 00000000..e901e5ba
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_accessory.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_accessory_active.png b/src/main/resources/assets/notenoughupdates/sort_accessory_active.png
new file mode 100644
index 00000000..dcc4d985
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_accessory_active.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_all.png b/src/main/resources/assets/notenoughupdates/sort_all.png
new file mode 100644
index 00000000..d7f2a1e3
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_all.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_all_active.png b/src/main/resources/assets/notenoughupdates/sort_all_active.png
new file mode 100644
index 00000000..fe2cc803
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_all_active.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_armor.png b/src/main/resources/assets/notenoughupdates/sort_armor.png
new file mode 100644
index 00000000..fab73503
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_armor.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_armor_active.png b/src/main/resources/assets/notenoughupdates/sort_armor_active.png
new file mode 100644
index 00000000..37135164
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_armor_active.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_mob.png b/src/main/resources/assets/notenoughupdates/sort_mob.png
new file mode 100644
index 00000000..1049e056
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_mob.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_mob_active.png b/src/main/resources/assets/notenoughupdates/sort_mob_active.png
new file mode 100644
index 00000000..90ef509e
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_mob_active.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_pet.png b/src/main/resources/assets/notenoughupdates/sort_pet.png
new file mode 100644
index 00000000..72bcb674
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_pet.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_pet_active.png b/src/main/resources/assets/notenoughupdates/sort_pet_active.png
new file mode 100644
index 00000000..ea0c9852
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_pet_active.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_weapon.png b/src/main/resources/assets/notenoughupdates/sort_weapon.png
new file mode 100644
index 00000000..f3292f66
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_weapon.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/sort_weapon_active.png b/src/main/resources/assets/notenoughupdates/sort_weapon_active.png
new file mode 100644
index 00000000..f6854ce5
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/sort_weapon_active.png
Binary files differ
diff --git a/src/main/resources/assets/notenoughupdates/wkhtmltox.zip b/src/main/resources/assets/notenoughupdates/wkhtmltox.zip
new file mode 100644
index 00000000..5717aeaf
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/wkhtmltox.zip
Binary files differ
diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info
new file mode 100644
index 00000000..d8192fc8
--- /dev/null
+++ b/src/main/resources/mcmod.info
@@ -0,0 +1,16 @@
+[
+{
+ "modid": "notenoughupdates",
+ "name": "NotEnoughUpdates",
+ "description": "Mod that shows recipes and useful info for Hypixel's Skyblock gamemode.",
+ "version": "${version}",
+ "mcversion": "${mcversion}",
+ "url": "",
+ "updateUrl": "",
+ "authorList": ["Moulberry"],
+ "credits": "Nullzee for listening to me rant. Contributors, etc. <3",
+ "logoFile": "",
+ "screenshots": [],
+ "dependencies": []
+}
+]
diff --git a/src/main/resources/mixins.notenoughupdates.json b/src/main/resources/mixins.notenoughupdates.json
new file mode 100644
index 00000000..e3a1f0cb
--- /dev/null
+++ b/src/main/resources/mixins.notenoughupdates.json
@@ -0,0 +1,8 @@
+{
+ "package": "io.github.moulberry.notenoughupdates.mixins",
+ "refmap": "mixins.notenoughupdates.refmap.json",
+ "compatibilityLevel": "JAVA_8",
+ "mixins": [
+ "MixinItemStack"
+ ]
+} \ No newline at end of file