diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..2c770e09fe --- /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 0000000000..29128fba70 --- /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 0000000000..4687f10d44 --- /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 0000000000..30d399d8d2 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..671daab242 --- /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 0000000000..91a7e269e1 --- /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 0000000000..8a0b282aa6 --- /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 0000000000..3cf5ef31c7 --- /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 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 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("'); + } + } +} 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 0000000000..bf194e333a --- /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 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 0000000000..6916372b68 --- /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= 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 0000000000..ceb4e5d4cf --- /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 craftMatrices; + private List 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 craftMatrices, List 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 + 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 0000000000..48db19a518 --- /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 0000000000..a89bd1dc0c --- /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 getChangedItems(Map oldShas) { + HashMap 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 getItemsDownload(Set filename) { + HashMap 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 0000000000..08939cca55 --- /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 itemMap = new TreeMap<>(); + + private TreeMap> tagWordMap = new TreeMap<>(); + private TreeMap>> titleWordMap = new TreeMap<>(); + private TreeMap>> 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 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 craftCost = new HashMap<>(); + + private HashMap> 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 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 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 oldShas = new HashMap<>(); + for (Map.Entry entry : itemShaConfig.entrySet()) { + if (new File(itemsLocation, entry.getKey() + ".json").exists()) { + oldShas.put(entry.getKey() + ".json", entry.getValue().getAsString()); + } + } + Map changedFiles = neuio.getChangedItems(oldShas); + + if (changedFiles != null) { + for (Map.Entry 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 downloads = neuio.getItemsDownload(changedFiles.keySet()); + + String startMessage = "NotEnoughUpdates: Syncing with remote repository ("; + int downloaded = 0; + + for (Map.Entry 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 search(String query) { + LinkedHashSet 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 search(String query, TreeMap>> wordMap) { + HashMap> matches = null; + + query = clean(query).toLowerCase(); + for(String queryWord : query.split(" ")) { + HashMap> matchesToKeep = new HashMap<>(); + for(HashMap> 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 Map subMapWithKeysThatAreSuffixes(String prefix, NavigableMap 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 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 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 craftMatrices = new ArrayList<>(); + List 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 0000000000..e60ca14f2e --- /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 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= 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= 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(""); + wiki = split[split.length - 1]; //Remove everything before infobox + wiki = wiki.split("")[0]; //Remove navbox + wiki = wiki.split(" internalname = new AtomicReference<>(null); + AtomicReference 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 getCompareAscending() { + return manager.config.compareAscending.value; + } + private List getFavourites() { + return manager.config.favourites.value; + } + + private Comparator 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= 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 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... classes) { + EntityLivingBase[] entities = new EntityLivingBase[classes.length]; + try { + EntityLivingBase last = null; + for(int i=0; i 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 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 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 text = new ArrayList<>(); + text.add(json.get("displayname").getAsString()); + JsonArray lore = json.get("lore").getAsJsonArray(); + for(int i=0; i 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 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[] 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 clazz = EntityList.stringToClassMapping.get(entityrender.get(i).getAsString()); + if (clazz != null && EntityLivingBase.class.isAssignableFrom(clazz)) { + entities[i] = (Class) clazz; + } + } + } else if (json.get("entityrender").isJsonPrimitive()) { + Class clazz = EntityList.stringToClassMapping.get(json.get("entityrender").getAsString()); + if (clazz != null && EntityLivingBase.class.isAssignableFrom(clazz)) { + entities[0] = (Class) 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 0000000000..28b35a0b57 --- /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 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 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 ArrayList createList(T... values) { + ArrayList 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++ 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 wrappedTextLines = new ArrayList(); + for (int i = 0; i < textLines.size(); i++) + { + String textLine = textLines.get(i); + List 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 0000000000..0abca240d4 --- /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 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 add = new ArrayList<>(); + for(Map.Entry 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 add = new ArrayList<>(); + for(Map.Entry 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 0000000000..49cc53896e --- /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 0000000000..6469ff1c01 --- /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(""); + wiki = split[split.length - 1]; //Remove everything before infobox + wiki = wiki.split("")[0]; //Remove navbox + wiki = wiki.split("
"+html+""; + html = "
"+html+"
"; + html = "\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 pixels = new ArrayList<>(); + + int skip = IMAGE_WIDTH/EXT_WIDTH+1; + + for(int y=0; y15sec). 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 0000000000..74321081e5 --- /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 0000000000..bf1a987e15 --- /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 accessoryMap = new LinkedHashMap<>(); + private LinkedHashMap allTalismans = new LinkedHashMap<>(); + private List 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 rarityComparator = new Comparator() { + @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 all = new TreeSet<>(rarityComparator); + LinkedHashMap highestRarity = new LinkedHashMap<>(); + LinkedHashMap lowerRarity = new LinkedHashMap<>(); + + for(Map.Entry 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 entry : highestRarity.entrySet()) { + allTalismans.put(entry.getKey(), entry.getValue()); + } + for(Map.Entry entry : lowerRarity.entrySet()) { + allTalismans.put(entry.getKey(), entry.getValue()); + } + + HashMap 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= maxAccessories) { + lowestRarity = 999; + for(Map.Entry 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 valueMap = new TreeMap<>(); + outer: + for(Map.Entry 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 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 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 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 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 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 0000000000..056aeaf166 --- /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 0000000000..31f6dca978 --- /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, 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> textToDisplay = new AtomicReference<>(null); + AtomicReference 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 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)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 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 0000000000..388719d0a8 --- /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 0000000000..06a8810de2 --- /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 0000000000..0ed03c36d1 --- /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 0000000000..28bc9b718f --- /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 0000000000..1112a88216 --- /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 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= 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 0000000000..5591fcb924 --- /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 options = new ArrayList<>(); + private List 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 internalname; + private Supplier itemid; + private Supplier displayname; + private Supplier lore; + private Supplier crafttext; + private Supplier infoType; + private Supplier info; + private Supplier clickcommand; + private Supplier 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 { + 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 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 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 0000000000..4a71321879 --- /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 0000000000..6c7a70a018 --- /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 enableItemEditing = new Option( + false, + "Enable Item Editing", + true, + "Dev Feature. Please don't use."); + public Option onlyShowOnSkyblock = new Option( + true, + "Only Show On Skyblock", + false, + "GUI Overlay only appears when you are playing Skyblock."); + public Option advancedPriceInfo = new Option( + false, + "Advanced Price Information", + false, + "Shows some extra information about item sales."); + public Option cacheRenderedItempane = new Option( + true, + "Cache Rendered Itempane", + false, + "Caches the drawn itempane, drastically improving performance. However, animated textures will not work."); + public Option bgBlurFactor = new Option( + 5.0, + "Background Blur Factor", + false, + "Changes the strength of pane background blur. 0-50."); + public Option apiKey = new Option( + "", + "Api key used for certain features.", + false, + "Type /api new to receive key and put it here."); + public Option 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 keepopen = new Option( + false, + "Keep Itempane Open", + false, + "If true, the itempane will stay open after the gui is closed."); + public Option itemStyle = new Option( + true, + "Circular Item BG Style", + false, + "If true, uses the circular item background style instead of the square style."); + public Option 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 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 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 dev = new Option( + false, + "Show Dev Options", + true, + "Dev Feature. Please don't use."); + public Option compareMode = new Option( + 0.0, + "Compare Mode", + false, + "Compare Mode"); + public Option sortMode = new Option( + 0.0, + "Sort Mode", + false, + "Sort Mode"); + public Option> compareAscending = new Option( + Utils.createList(true, true), + "Compare Ascending", + false, + "Compare Ascending"); + public Option> favourites = new Option( + new ArrayList(), + "Favourites", + false, + "Favourites"); + + public List