diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6effbc9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# This is the universal Text Editor Configuration +# for all GTNewHorizons projects +# See: https://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{bat,ini}] +end_of_line = crlf + +[*.{dtd,json,info,mcmeta,md,sh,svg,xml,xsd,xsl,yaml,yml}] +indent_size = 2 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..7749bd3 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Ignore spotlessApply reformat +955a875d42bd8f725d389ef8c4f571824f2ae0c8 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fd2792b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,44 @@ +* text eol=lf + +*.[jJ][aA][rR] binary + +*.[pP][nN][gG] binary +*.[jJ][pP][gG] binary +*.[jJ][pP][eE][gG] binary +*.[gG][iI][fF] binary +*.[tT][iI][fF] binary +*.[tT][iI][fF][fF] binary +*.[iI][cC][oO] binary +*.[sS][vV][gG] text +*.[eE][pP][sS] binary +*.[xX][cC][fF] binary + +*.[kK][aA][rR] binary +*.[mM]4[aA] binary +*.[mM][iI][dD] binary +*.[mM][iI][dD][iI] binary +*.[mM][pP]3 binary +*.[oO][gG][gG] binary +*.[rR][aA] binary + +*.7[zZ] binary +*.[gG][zZ] binary +*.[tT][aA][rR] binary +*.[tT][gG][zZ] binary +*.[zZ][iI][pP] binary + +*.[tT][cC][nN] binary +*.[sS][oO] binary +*.[dD][lL][lL] binary +*.[dD][yY][lL][iI][bB] binary +*.[pP][sS][dD] binary +*.[tT][tT][fF] binary +*.[oO][tT][fF] binary + +*.[pP][aA][tT][cC][hH] -text + +*.[bB][aA][tT] text eol=crlf +*.[cC][mM][dD] text eol=crlf +*.[pP][sS]1 text eol=crlf + +*[aA][uU][tT][oO][gG][eE][nN][eE][rR][aA][tT][eE][dD]* binary diff --git a/.github/ISSUE_TEMPLATE/000-report-bug.yml b/.github/ISSUE_TEMPLATE/000-report-bug.yml new file mode 100644 index 0000000..f2b2710 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/000-report-bug.yml @@ -0,0 +1,62 @@ +name: Report Bug +description: "Report a bug." +body: + - type: markdown + attributes: + value: "A bug/crash report with sufficient information and logs to reproduce and track down." + - type: input + id: discord + attributes: + label: Your GTNH Discord Username + description: Leave empty if you don't have one, but this will make it harder to contact you if we need additional info. + placeholder: "Example: Fake#1234" + - type: input + id: version + attributes: + label: Mod Version + description: "What version of the Mod are you using?" + placeholder: "Example: 1.0.0" + validations: + required: true + - type: textarea + id: report + attributes: + label: Bug Report + description: "Relevant information, as well as relevant logs attached such as `logs/fml-client-latest.log." + placeholder: "Example: https://mclo.gs/ OR submit the file to github by dragging it to this textbox." + validations: + required: true + - type: dropdown + id: java + attributes: + label: Java Version + description: What Java version are you using? It's worth mentioning that if you play on Java9+ you should try update to latest minor release (e.g. prefer Java 17.0.6 over 17.0.2) of that version. + options: + - Java 8 + - Java 9 + - Java 11 + - Java 17 + - Java 19 + - Java 20 + - Java 21 + - Other (Please Specify) + validations: + required: true + - type: textarea + id: modlist + attributes: + label: Mod List or GTNH Pack Version + description: "List of mods, ideally a minimal reproducible set (can be retrieved from latest.log). If using GTNH please indicate pack version and any changed mods, not the entire modlist." + placeholder: "List of mods or GTNH Pack version goes here" + validations: + required: true + - type: checkboxes + id: final + attributes: + label: Final Checklist + description: Certify that you read things + options: + - label: "I have searched the issues and haven't found a similar issue." + required: true + + diff --git a/.github/ISSUE_TEMPLATE/001-request-feature.yml b/.github/ISSUE_TEMPLATE/001-request-feature.yml new file mode 100644 index 0000000..cb325a1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/001-request-feature.yml @@ -0,0 +1,28 @@ +name: Feature Request +description: "Request a feature." +body: + - type: markdown + attributes: + value: "Please use this form to request features. We ask you check if it was already requested though" + - type: input + id: discord + attributes: + label: Your GTNH Discord Username + description: Leave empty if you don't have one, but this will make it harder to contact you if we need additional info. + placeholder: "Example: Wumpus#1234" + - type: textarea + id: request + attributes: + label: Feature Request + description: "Relevant information, as well as relevant images" + placeholder: "Example: https://mclo.gs/ OR submit the file to github by dragging it to this textbox." + validations: + required: true + - type: checkboxes + id: final + attributes: + label: Final Checklist + description: Certify that you read things + options: + - label: "I have searched the issues and haven't found a similar issue." + required: true diff --git a/.github/scripts/test_no_error_reports b/.github/scripts/test_no_error_reports new file mode 100755 index 0000000..1fcc739 --- /dev/null +++ b/.github/scripts/test_no_error_reports @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# bashsupport disable=BP5006 # Global environment variables +RUNDIR="run" \ + CRASH="crash-reports" \ + SERVERLOG="server.log" + +# enable nullglob to get 0 results when no match rather than the pattern +shopt -s nullglob + +# store matches in array +crash_reports=("$RUNDIR/$CRASH/crash"*.txt) + +# if array not empty there are crash_reports +if [ "${#crash_reports[@]}" -gt 0 ]; then + # get the latest crash_report from array + latest_crash_report="${crash_reports[-1]}" + { + printf 'Latest crash report detected %s:\n' "${latest_crash_report##*/}" + cat "$latest_crash_report" + } >&2 + exit 1 +fi + +if grep --quiet --fixed-strings 'Fatal errors were detected' "$SERVERLOG"; then + { + printf 'Fatal errors detected:\n' + cat server.log + } >&2 + exit 1 +fi + +if grep --quiet --fixed-strings 'The state engine was in incorrect state ERRORED and forced into state SERVER_STOPPED' \ + "$SERVERLOG"; then + { + printf 'Server force stopped:' + cat server.log + } >&2 + exit 1 +fi + +if ! grep --quiet --perl-regexp --only-matching '.+Done \(.+\)\! For help, type "help" or "\?"' "$SERVERLOG"; then + { + printf 'Server did not finish startup:' + cat server.log + } >&2 + exit 1 +fi + +printf 'No crash reports detected' +exit 0 diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..3ee2f68 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,13 @@ + +name: Build and test + +on: + pull_request: + branches: [ master, main ] + push: + branches: [ master, main ] + +jobs: + build-and-test: + uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master + secrets: inherit diff --git a/.github/workflows/release-tags.yml b/.github/workflows/release-tags.yml new file mode 100644 index 0000000..e4c0be6 --- /dev/null +++ b/.github/workflows/release-tags.yml @@ -0,0 +1,14 @@ + +name: Release tagged build + +on: + push: + tags: [ '*' ] + +permissions: + contents: write + +jobs: + release-tags: + uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master + secrets: inherit diff --git a/.gitignore b/.gitignore index ccc5e8b..40fb5e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,29 @@ -/build/build -/build/.gradle +.gradle +.settings +/.idea/ +/run/ +/build/ +/eclipse/ +.classpath +.project +/bin/ +/config/ +/crash-reports/ +/logs/ +options.txt +/saves/ +usernamecache.json +banned-ips.json +banned-players.json +eula.txt +ops.json +server.properties +servers.dat +usercache.json +whitelist.json +/out/ +*.iml +*.ipr +*.iws +src/main/resources/mixins.*.json +*.bat diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..a6b5f68 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +# Any Github changes require admin approval +/.github/** @GTNewHorizons/admin + diff --git a/addon.gradle b/addon.gradle new file mode 100644 index 0000000..8d81619 --- /dev/null +++ b/addon.gradle @@ -0,0 +1,8 @@ +minecraft { + if (replaceGradleTokenInFile) { + tagReplacementFiles.add "CodeChickenCorePlugin.java" + if(gradleTokenVersion) { + injectedTags.put gradleTokenVersion, versionDetails().lastTag + } + } +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..9915e20 --- /dev/null +++ b/build.gradle @@ -0,0 +1,10 @@ +//version: 1707058017 + +plugins { + id 'com.gtnewhorizons.gtnhconvention' +} + +minecraft { + //extraRunJvmArguments.add("-Dccc.dev.runtimePublic=true") + //extraRunJvmArguments.add("-Dccc.dev.deobfuscate=true") +} diff --git a/build/build.gradle b/build/build.gradle deleted file mode 100644 index cb4be9e..0000000 --- a/build/build.gradle +++ /dev/null @@ -1,113 +0,0 @@ -buildscript { - repositories { - mavenCentral() - maven { - name = "forge" - url = "http://files.minecraftforge.net/maven" - } - maven { - name = "sonatype" - url = "https://oss.sonatype.org/content/repositories/snapshots/" - } - } - dependencies { - classpath 'net.minecraftforge.gradle:ForgeGradle:1.2-SNAPSHOT' - } -} - -apply plugin: 'forge' - -group = "codechicken" // http://maven.apache.org/guides/mini/guide-naming-conventions.html -archivesBaseName = "CodeChickenCore" - -// Define properties file -ext.configFile = file "build.properties" - -configFile.withReader { - // Load config. It shall from now be referenced as simply config or project.config - def prop = new Properties() - prop.load(it) - project.ext.config = new ConfigSlurper().parse prop -} - -dependencies { - compile "codechicken:CodeChickenLib:${config.mc_version}-${config.ccl_version}:dev" -} - -version = "${project.config.mod_version}." + System.getenv("BUILD_NUMBER") ?: "1" - -println config.mc_version + "-" + config.forge_version -// Setup the forge minecraft plugin data. Specify the preferred forge/minecraft version here -minecraft { - version = config.mc_version + "-" + config.forge_version - replace '${mod_version}', project.config.mod_version -} - -sourceSets { - main { - def root = project.projectDir.parentFile - java { - srcDir new File(root, "src") - } - resources { - srcDir new File(root, "resources") - } - } -} - -processResources { - //redo task if any of these properties change - inputs.property "version", project.version - inputs.property "mc_version", config.mc_version - inputs.property "ccl_version", config.ccl_version - - // Replace properties in info files - from(sourceSets.main.resources.srcDirs) { - include '*.info' - expand 'version':project.version,'mc_version':config.mc_version,'ccl_version':config.ccl_version - } - // Copy everything else - from(sourceSets.main.resources.srcDirs) { - include 'assets/**/*.*' - } -} - -version = "${project.minecraft.version}-${project.version}" -def commonManifest = { - attributes 'FMLCorePlugin': 'codechicken.core.launch.CodeChickenCorePlugin' -} - -jar { - classifier = 'universal' - manifest commonManifest -} - -task sourceJar(type: Jar) { - from sourceSets.main.java - classifier = 'src' -} - -task devJar(type: Jar) { - from sourceSets.main.output - classifier = 'dev' - manifest commonManifest -} - -// Tell the artifact system about our extra jars -artifacts { - archives sourceJar, devJar -} - -// Configure an upload task. -uploadArchives { - dependsOn 'reobf' - if (project.hasProperty("local_maven")) { - repositories { - logger.info('Publishing to local maven repo') - - mavenDeployer { - repository(url: "file://${local_maven}") - } - } - } -} \ No newline at end of file diff --git a/build/build.properties b/build/build.properties deleted file mode 100644 index 9abbab6..0000000 --- a/build/build.properties +++ /dev/null @@ -1,4 +0,0 @@ -mc_version=1.7.10 -forge_version=10.13.3.1373-1.7.10 -ccl_version=1.1.3.138 -mod_version=1.0.7 diff --git a/build/gradle/wrapper/gradle-wrapper.jar b/build/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 3c7abdf..0000000 Binary files a/build/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/build/gradle/wrapper/gradle-wrapper.properties b/build/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 2e8c946..0000000 --- a/build/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Wed Mar 26 13:33:58 CDT 2014 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-2.0-bin.zip diff --git a/build/gradlew b/build/gradlew deleted file mode 100644 index 91a7e26..0000000 --- a/build/gradlew +++ /dev/null @@ -1,164 +0,0 @@ -#!/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/build/settings.gradle b/build/settings.gradle deleted file mode 100644 index 25615e7..0000000 --- a/build/settings.gradle +++ /dev/null @@ -1,19 +0,0 @@ -/* - * This settings file was auto generated by the Gradle buildInit task - * by 'sunstrike' at '21/11/13 14:14' with Gradle 1.9 - * - * The settings file is used to specify which projects to include in your build. - * In a single project build this file can be empty or even removed. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user guide at http://gradle.org/docs/1.9/userguide/multi_project_builds.html - */ - -/* -// To declare projects as part of a multi-project build use the 'include' method -include 'shared' -include 'api' -include 'services:webservice' -*/ - -rootProject.name = 'CodeChickenCore' diff --git a/dependencies.gradle b/dependencies.gradle new file mode 100644 index 0000000..2767713 --- /dev/null +++ b/dependencies.gradle @@ -0,0 +1,4 @@ +// Add your dependencies here + +dependencies { +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..c0aecb5 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,205 @@ +# ExampleMod tag to use as Blowdryer (Spotless, etc.) settings version, leave empty to disable. +# LOCAL to test local config updates. +gtnh.settings.blowdryerTag = 0.2.0 + +# Human-readable mod name, available for mcmod.info population. +modName = CodeChicken Core + +# Case-sensitive identifier string, available for mcmod.info population and used for automatic mixin JSON generation. +# Conventionally lowercase. +modId = CodeChickenCore + +# Root package of the mod, used to find various classes in other properties, +# mcmod.info substitution, enabling assertions in run tasks, etc. +modGroup = codechicken + +# Whether to use modGroup as the maven publishing group. +# When false, com.github.GTNewHorizons is used. +useModGroupForPublishing = false + +# Updates your build.gradle and settings.gradle automatically whenever an update is available. +autoUpdateBuildScript = false + +# Version of Minecraft to target +minecraftVersion = 1.7.10 + +# Version of Minecraft Forge to target +forgeVersion = 10.13.4.1614 + +# Specify an MCP channel for dependency deobfuscation and the deobfParams task. +channel = stable + +# Specify an MCP mappings version for dependency deobfuscation and the deobfParams task. +mappingsVersion = 12 + +# Defines other MCP mappings for dependency deobfuscation. +remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/ + +# Select a default username for testing your mod. You can always override this per-run by running +# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE. +developmentEnvironmentUserName = Developer + +# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8. +# See https://github.com/bsideup/jabel for details on how this works. +enableModernJavaSyntax = true + +# Enables injecting missing generics into the decompiled source code for a better coding experience. +# Turns most publicly visible List, Map, etc. into proper List, Map types. +enableGenericInjection = true + +# Generate a class with a String field for the mod version named as defined below. +# If generateGradleTokenClass is empty or not missing, no such class will be generated. +# If gradleTokenVersion is empty or missing, the field will not be present in the class. +generateGradleTokenClass = codechicken.core.asm.Tags + +# Name of the token containing the project's current version to generate/replace. +gradleTokenVersion = VERSION + +# [DEPRECATED] Mod Group replacement token. +gradleTokenGroupName = + +# [DEPRECATED] +# Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java +# public static final String VERSION = "GRADLETOKEN_VERSION"; +# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's +# version in @Mod([...], version = VERSION, [...]). +# Leave these properties empty to skip individual token replacements. +replaceGradleTokenInFile = + +# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can +# leave this property empty. +# Example value: (apiPackage = api) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.api +apiPackage = + +# Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/META-INF/ +# There can be multiple files in a space-separated list. +# Example value: mymodid_at.cfg nei_at.cfg +accessTransformersFile = + +# Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled! +usesMixins = false + +# Set to a non-empty string to configure mixins in a separate source set under src/VALUE, instead of src/main. +# This can speed up compile times thanks to not running the mixin annotation processor on all input sources. +# Mixin classes will have access to "main" classes, but not the other way around. +separateMixinSourceSet = + +# Adds some debug arguments like verbose output and class export. +usesMixinDebug = false + +# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise. +mixinPlugin = + +# Specify the package that contains all of your Mixins. The package must exist or +# the build will fail. If you have a package property defined in your mixins..json, +# it must match with this or the build will fail. +mixinsPackage = + +# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin! +# This parameter is for legacy compatibility only +# Example value: (coreModClass = asm.FMLPlugin) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.asm.FMLPlugin +coreModClass = core.launch.CodeChickenCorePlugin + +# If your project is only a consolidation of mixins or a core mod and does NOT contain a 'normal' mod ( = some class +# that is annotated with @Mod) you want this to be true. When in doubt: leave it on false! +containsMixinsAndOrCoreModOnly = true + +# Enables Mixins even if this mod doesn't use them, useful if one of the dependencies uses mixins. +forceEnableMixins = false + +# If enabled, you may use 'shadowCompile' for dependencies. They will be integrated into your jar. It is your +# responsibility to check the license and request permission for distribution if required. +usesShadowedDependencies = false + +# If disabled, won't remove unused classes from shadowed dependencies. Some libraries use reflection to access +# their own classes, making the minimization unreliable. +minimizeShadowedDependencies = true + +# If disabled, won't rename the shadowed classes. +relocateShadowedDependencies = true + +# Adds CurseMaven, Modrinth, and some more well-known 1.7.10 repositories. +includeWellKnownRepositories = true + +# A list of repositories to exclude from the includeWellKnownRepositories setting. Should be a space separated +# list of strings, with the acceptable keys being(case does not matter): +# cursemaven +# modrinth +excludeWellKnownRepositories = + +# Change these to your Maven coordinates if you want to publish to a custom Maven repository instead of the default GTNH Maven. +# Authenticate with the MAVEN_USER and MAVEN_PASSWORD environment variables. +# If you need a more complex setup disable maven publishing here and add a publishing repository to addon.gradle. +usesMavenPublishing = true + +# Maven repository to publish the mod to. +# mavenPublishUrl = https\://nexus.gtnewhorizons.com/repository/releases/ + +# Publishing to Modrinth requires you to set the MODRINTH_TOKEN environment variable to your current Modrinth API token. +# +# The project's ID on Modrinth. Can be either the slug or the ID. +# Leave this empty if you don't want to publish to Modrinth. +modrinthProjectId = codechickencore-unofficial + +# The project's relations on Modrinth. You can use this to refer to other projects on Modrinth. +# Syntax: scope1-type1:name1;scope2-type2:name2;... +# Where scope can be one of [required, optional, incompatible, embedded], +# type can be one of [project, version], +# and the name is the Modrinth project or version slug/id of the other mod. +# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech +# Note: UniMixins is automatically set as a required dependency if usesMixins = true. +modrinthRelations = + +# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens. +# +# The project's numeric ID on CurseForge. You can find this in the About Project box. +# Leave this empty if you don't want to publish on CurseForge. +curseForgeProjectId = 746279 + +# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge. +# Syntax: type1:name1;type2:name2;... +# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible], +# and the name is the CurseForge project slug of the other mod. +# Example: requiredDependency:railcraft;embeddedLibrary:cofhlib;incompatible:buildcraft +# Note: UniMixins is automatically set as a required dependency if usesMixins = true. +curseForgeRelations = + +# Optional parameter to customize the produced artifacts. Use this to preserve artifact naming when migrating older +# projects. New projects should not use this parameter. +# customArchiveBaseName = + +# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory. +# runClientWorkingDirectory = run/client + +# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory. +# runServerWorkingDirectory = run/server + +# Optional parameter to have the build automatically fail if an illegal version is used. +# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'. +# The check is ONLY performed if the version is a git tag. +# Note: the specified string must be escaped, so e.g. 1\\.1\\.\\d+ instead of 1\.1\.\d+ +# versionPattern = + +# Uncomment to prevent the source code from being published. +# noPublishedSources = true + +# Uncomment this to disable Spotless checks. +# This should only be uncommented to keep it easier to sync with upstream/other forks. +# That is, if there is no other active fork/upstream, NEVER change this. +# disableSpotless = true + +# Uncomment this to disable Checkstyle checks (currently wildcard import check). +# disableCheckstyle = true + +# Override the IDEA build type. Valid values are: "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle" +# (force use delegated build). +# This is meant to be set in $HOME/.gradle/gradle.properties. +# e.g. add "systemProp.org.gradle.project.ideaOverrideBuildType=idea" will override the build type to be native build. +# WARNING: If you do use this option, it will overwrite whatever you have in your existing projects. This might not be what you want! +# Usually there is no need to uncomment this here as other developers do not necessarily use the same build type as you. +# ideaOverrideBuildType = idea + +# Whether IDEA should run spotless checks when pressing the Build button. +# This is meant to be set in $HOME/.gradle/gradle.properties. +# ideaCheckSpotlessOnBuild = true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..9bbc975 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 0000000..37f853b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..faf9300 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/build/gradlew.bat b/gradlew.bat similarity index 51% rename from build/gradlew.bat rename to gradlew.bat index 8a0b282..9d21a21 100644 --- a/build/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,22 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -8,26 +26,30 @@ @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=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + @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 +if %ERRORLEVEL% equ 0 goto execute -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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,54 +57,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 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% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 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 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..09bbb51 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,2 @@ +before_install: + - ./gradlew setupCIWorkspace \ No newline at end of file diff --git a/repositories.gradle b/repositories.gradle new file mode 100644 index 0000000..51d4dea --- /dev/null +++ b/repositories.gradle @@ -0,0 +1,5 @@ +// Add any additional repositiroes for your dependencies here + +repositories { + mavenLocal() +} diff --git a/resources/cccmod.info b/resources/cccmod.info deleted file mode 100644 index aedcd8d..0000000 --- a/resources/cccmod.info +++ /dev/null @@ -1,13 +0,0 @@ -[ -{ - "modid": "CodeChickenCore", - "name": "CodeChicken Core", - "description": "Base common code for all chickenbones mods. - -Supporters: JBoyJr", - "version": "${version}", - "mcversion": "${mc_version}", - "url": "http://www.minecraftforum.net/topic/909223", - "authorList": [ "ChickenBones" ] -} -] \ No newline at end of file diff --git a/resources/dependencies.info b/resources/dependencies.info deleted file mode 100644 index c40cb0a..0000000 --- a/resources/dependencies.info +++ /dev/null @@ -1,7 +0,0 @@ -{ - "repo": "http://files.minecraftforge.net/maven/codechicken/CodeChickenLib/${mc_version}-${ccl_version}/", - "file": "CodeChickenLib-${mc_version}-${ccl_version}-universal.jar", - "dev": "CodeChickenLib-${mc_version}-${ccl_version}-dev.jar", - "class": "codechicken.lib.asm.ASMHelper", - "coreLib": true -} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e883289 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,23 @@ + +pluginManagement { + repositories { + maven { + // RetroFuturaGradle + name "GTNH Maven" + url "https://nexus.gtnewhorizons.com/repository/public/" + mavenContent { + includeGroup("com.gtnewhorizons") + includeGroupByRegex("com\\.gtnewhorizons\\..+") + } + } + gradlePluginPortal() + mavenCentral() + mavenLocal() + } +} + +plugins { + id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.48' +} + + diff --git a/src/codechicken/core/CCUpdateChecker.java b/src/codechicken/core/CCUpdateChecker.java deleted file mode 100644 index 5616434..0000000 --- a/src/codechicken/core/CCUpdateChecker.java +++ /dev/null @@ -1,111 +0,0 @@ -package codechicken.core; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.SocketTimeoutException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.ArrayList; - -import codechicken.core.launch.CodeChickenCorePlugin; -import com.google.common.base.Function; -import cpw.mods.fml.common.Loader; -import cpw.mods.fml.common.versioning.ComparableVersion; -import cpw.mods.fml.relauncher.FMLInjectionData; - -import net.minecraft.client.Minecraft; -import net.minecraft.util.ChatComponentText; -import net.minecraft.util.StatCollector; - -public class CCUpdateChecker -{ - private static final ArrayList updates = new ArrayList(); - - private static class ThreadUpdateCheck extends Thread - { - private final URL url; - private final Function handler; - - public ThreadUpdateCheck(URL url, Function handler) { - this.url = url; - this.handler = handler; - - setName("CodeChicken Update Checker"); - } - - @Override - public void run() { - try { - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - BufferedReader read = new BufferedReader(new InputStreamReader(conn.getInputStream())); - String ret = read.readLine(); - read.close(); - if(ret == null) ret = ""; - handler.apply(ret); - } catch (SocketTimeoutException ignored) {} - catch (UnknownHostException ignored) {} - catch (IOException iox) { - iox.printStackTrace(); - } - } - } - - public static void tick() { - Minecraft mc = Minecraft.getMinecraft(); - if (!mc.inGameHasFocus) - return; - - synchronized (updates) { - for (String s : updates) - mc.thePlayer.addChatMessage(new ChatComponentText(s)); - updates.clear(); - } - } - - public static void addUpdateMessage(String s) { - synchronized (updates) { - updates.add(s); - } - } - - public static String mcVersion() { - return (String) FMLInjectionData.data()[4]; - } - - public static void updateCheck(final String mod, final String version) { - updateCheck("http://www.chickenbones.net/Files/notification/version.php?" + - "version=" + mcVersion() + "&" + - "file=" + mod, - new Function() - { - @Override public Void apply(String ret) { - if (!ret.startsWith("Ret: ")) { - CodeChickenCorePlugin.logger.error("Failed to check update for " + mod + " returned: " + ret); - return null; - } - ComparableVersion newversion = new ComparableVersion(ret.substring(5)); - if (newversion.compareTo(new ComparableVersion(version)) > 0) - addUpdateMessage(StatCollector.translateToLocalFormatted("codechickencore.update", newversion, mod)); - return null; - } - }); - } - - public static void updateCheck(String mod) { - updateCheck(mod, Loader.instance().getIndexedModList().get(mod).getVersion()); - } - - public static void updateCheck(String url, Function handler) { - try { - new ThreadUpdateCheck(new URL(url), handler).start(); - } catch (MalformedURLException e) { - CodeChickenCorePlugin.logger.error("Malformed URL: "+url, e); - } - } -} diff --git a/src/codechicken/core/ReflectionManager.java b/src/codechicken/core/ReflectionManager.java deleted file mode 100644 index c52c92d..0000000 --- a/src/codechicken/core/ReflectionManager.java +++ /dev/null @@ -1,316 +0,0 @@ -package codechicken.core; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; - -import codechicken.lib.asm.ObfMapping; - -public class ReflectionManager -{ - public static HashMap, Class> primitiveWrappers = new HashMap, Class>(); - - static - { - primitiveWrappers.put(Integer.TYPE, Integer.class); - primitiveWrappers.put(Short.TYPE, Short.class); - primitiveWrappers.put(Byte.TYPE, Byte.class); - primitiveWrappers.put(Long.TYPE, Long.class); - primitiveWrappers.put(Double.TYPE, Double.class); - primitiveWrappers.put(Float.TYPE, Float.class); - primitiveWrappers.put(Boolean.TYPE, Boolean.class); - primitiveWrappers.put(Character.TYPE, Character.class); - } - - public static boolean isInstance(Class class1, Object obj) - { - Class primitive = primitiveWrappers.get(class1); - if(primitive != null) - { - if(primitive == Long.class && Long.class.isInstance(obj)) - return true; - if((primitive == Long.class || primitive == Integer.class) && Integer.class.isInstance(obj)) - return true; - if((primitive == Long.class || primitive == Integer.class || primitive == Short.class) && Short.class.isInstance(obj)) - return true; - if((primitive == Long.class || primitive == Integer.class || primitive == Short.class || primitive == Byte.class) && Integer.class.isInstance(obj)) - return true; - - if(primitive == Double.class && Double.class.isInstance(obj)) - return true; - if((primitive == Double.class || primitive == Float.class) && Float.class.isInstance(obj)) - return true; - - return primitive.isInstance(obj); - } - return class1.isInstance(obj); - } - - public static Class findClass(String name) - { - return findClass(name, true); - } - - public static boolean classExists(String name) - { - return findClass(name, false) != null; - } - - public static Class findClass(String name, boolean init) - { - try - { - return Class.forName(name, init, ReflectionManager.class.getClassLoader()); - } - catch (ClassNotFoundException cnfe) - { - try - { - return Class.forName("net.minecraft.src."+name, init, ReflectionManager.class.getClassLoader()); - } - catch (ClassNotFoundException cnfe2) - { - return null; - } - } - } - - public static void setField(Class class1, Object instance, String name, Object value) throws IllegalArgumentException, IllegalAccessException - { - setField(class1, instance, new String[]{name}, value); - } - - public static void setField(Class class1, Object instance, String[] names, Object value) throws IllegalArgumentException, IllegalAccessException - { - for(Field field : class1.getDeclaredFields()) - { - boolean match = false; - for(String name : names) - { - if(field.getName().equals(name)) - { - match = true; - break; - } - } - if(!match) - { - continue; - } - - field.setAccessible(true); - field.set(instance, value); - return; - } - } - - public static void setField(Class class1, Object instance, int fieldindex, Object value) throws IllegalArgumentException, IllegalAccessException - { - Field field = class1.getDeclaredFields()[fieldindex]; - field.setAccessible(true); - field.set(instance, value); - } - - /** - * Static function - * void return type - * single name - */ - public static void callMethod(Class class1, String name, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException - { - callMethod(class1, null, new String[]{name}, params); - } - - /** - * Static function - * void return type - * single name - */ - public static void callMethod(Class class1, String[] names, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException - { - callMethod(class1, null, names, params); - } - - /** - * void return type - * single name - */ - public static void callMethod(Class class1, Object instance, String name, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException - { - callMethod(class1, null, instance, new String[]{name}, params); - } - - /** - * void return type - */ - public static void callMethod(Class class1, Object instance, String[] names, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException - { - callMethod(class1, null, instance, names, params); - } - - /** - * Static method - * single name - */ - public static R callMethod(Class class1, Class returntype, String name, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException - { - return callMethod(class1, returntype, null, new String[]{name}, params); - } - - /** - * Static method - */ - public static R callMethod(Class class1, Class returntype, String[] names, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException - { - return callMethod(class1, returntype, null, names, params); - } - - /** - * sinlge name - */ - public static R callMethod(Class class1, Class returntype, Object instance, String name, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException - { - return callMethod(class1, returntype, instance, new String[]{name}, params); - } - - public static R callMethod(Class class1, Class returntype, Object instance, String[] names, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException - { - nextMethod: for(Method method : class1.getDeclaredMethods()) - { - boolean match = false; - for(String name : names) - { - if(method.getName().equals(name)) - { - match = true; - break; - } - } - if(!match) - { - continue; - } - - Class[] paramtypes = method.getParameterTypes(); - if(paramtypes.length != params.length) - continue; - - for(int i = 0; i < params.length; i++) - { - if(!isInstance(paramtypes[i], params[i])) - continue nextMethod; - } - - method.setAccessible(true); - return (R)method.invoke(instance, params); - } - return null; - } - - public static T getField(Class class1, Class fieldType, Object instance, int fieldIndex) throws IllegalArgumentException, IllegalAccessException - { - Field field = class1.getDeclaredFields()[fieldIndex]; - field.setAccessible(true); - return (T) field.get(instance); - } - - public static T getField(Class class1, Class fieldType, Object instance, String fieldName) - { - try - { - Field field = class1.getDeclaredField(fieldName); - field.setAccessible(true); - return (T) field.get(instance); - } - catch(Exception e) - { - throw new RuntimeException(e); - } - } - - public static T newInstance(Class class1, Object... params) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException - { - nextMethod: for(Constructor constructor : class1.getDeclaredConstructors()) - { - Class[] paramtypes = constructor.getParameterTypes(); - if(paramtypes.length != params.length) - continue; - - for(int i = 0; i < params.length; i++) - { - if(!isInstance(paramtypes[i], params[i])) - continue nextMethod; - } - - constructor.setAccessible(true); - return (T)constructor.newInstance(params); - } - return null; - } - - public static boolean hasField(Class class1, String fieldName) - { - try - { - class1.getDeclaredField(fieldName); - return true; - } - catch(NoSuchFieldException nfe) - { - return false; - } - } - - public static T get(Field field, Class class1) - { - return get(field, class1, null); - } - - public static T get(Field field, Class class1, Object instance) - { - try - { - return (T)field.get(instance); - } - catch(Exception e) - { - throw new RuntimeException(e); - } - } - - public static void set(Field field, Object value) - { - set(field, null, value); - } - - public static void set(Field field, Object instance, Object value) - { - try - { - field.set(instance, value); - } - catch(Exception e) - { - throw new RuntimeException(e); - } - } - - public static Field getField(ObfMapping mapping) - { - mapping.toRuntime(); - - try - { - Class clazz = ReflectionManager.class.getClassLoader().loadClass(mapping.javaClass()); - Field field = clazz.getDeclaredField(mapping.s_name); - field.setAccessible(true); - return field; - } - catch(Exception e) - { - throw new RuntimeException(e); - } - } -} diff --git a/src/codechicken/core/asm/DefaultImplementationTransformer.java b/src/codechicken/core/asm/DefaultImplementationTransformer.java deleted file mode 100644 index df16917..0000000 --- a/src/codechicken/core/asm/DefaultImplementationTransformer.java +++ /dev/null @@ -1,94 +0,0 @@ -package codechicken.core.asm; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; - -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodNode; - -import codechicken.lib.asm.ASMHelper; -import codechicken.lib.asm.ClassHeirachyManager; -import codechicken.lib.asm.ObfMapping; - -import net.minecraft.launchwrapper.IClassTransformer; -import net.minecraft.launchwrapper.LaunchClassLoader; - -public class DefaultImplementationTransformer implements IClassTransformer -{ - private static LaunchClassLoader cl = (LaunchClassLoader)ClassHeirachyManager.class.getClassLoader(); - private static ClassNode getClassNode(String name) { - try { - return ASMHelper.createClassNode(cl.getClassBytes(name.replace('/', '.'))); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - static class InterfaceImpl - { - public final String iname; - public ArrayList impls = new ArrayList(); - - public InterfaceImpl(String iname, String cname) - { - this.iname = iname; - HashSet names = new HashSet(); - ClassNode inode = getClassNode(iname); - for(MethodNode method : inode.methods) - names.add(method.name+method.desc); - - ClassNode cnode = getClassNode(cname); - for(MethodNode method : cnode.methods) - if(names.contains(method.name+method.desc)) { - impls.add(method); - method.desc = new ObfMapping(cnode.name, method.name, method.desc).toRuntime().s_desc; - } - } - - public boolean patch(ClassNode cnode) { - LinkedList names = new LinkedList(); - for(MethodNode method : cnode.methods) { - ObfMapping m = new ObfMapping(cnode.name, method.name, method.desc).toRuntime(); - names.add(m.s_name+m.s_desc); - } - - boolean changed = false; - for(MethodNode impl : impls) { - if(names.contains(impl.name+impl.desc)) - continue; - - MethodNode copy = new MethodNode(impl.access, impl.name, impl.desc, - impl.signature, impl.exceptions == null ? null : impl.exceptions.toArray(new String[0])); - ASMHelper.copy(impl, copy); - cnode.methods.add(impl); - changed = true; - } - return changed; - } - } - - private static HashMap impls = new HashMap(); - - public static void registerDefaultImpl(String iname, String cname) { - impls.put(iname.replace('.', '/'), new InterfaceImpl(iname, cname)); - } - - @Override - public byte[] transform(String name, String transformedName, byte[] bytes) { - if(transformedName.startsWith("net.minecraft") || impls.isEmpty()) - return bytes; - - ClassNode cnode = ASMHelper.createClassNode(bytes); - boolean changed = false; - for(String iname : cnode.interfaces) { - InterfaceImpl impl = impls.get(iname); - if(impl != null) - changed |= impl.patch(cnode); - } - - return changed ? ASMHelper.createBytes(cnode, 0) : bytes; - } -} diff --git a/src/codechicken/core/asm/DelegatedTransformer.java b/src/codechicken/core/asm/DelegatedTransformer.java deleted file mode 100644 index c212469..0000000 --- a/src/codechicken/core/asm/DelegatedTransformer.java +++ /dev/null @@ -1,154 +0,0 @@ -package codechicken.core.asm; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Map; -import java.util.Stack; -import java.util.jar.JarFile; -import java.util.zip.ZipEntry; - -import net.minecraft.launchwrapper.Launch; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.Opcodes; - -import net.minecraft.launchwrapper.IClassTransformer; -import net.minecraft.launchwrapper.LaunchClassLoader; - -import static codechicken.core.launch.CodeChickenCorePlugin.logger; - -public class DelegatedTransformer implements IClassTransformer -{ - private static ArrayList delegatedTransformers; - private static Method m_defineClass; - private static Field f_cachedClasses; - - public DelegatedTransformer() - { - delegatedTransformers = new ArrayList(); - try - { - m_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE); - m_defineClass.setAccessible(true); - f_cachedClasses = LaunchClassLoader.class.getDeclaredField("cachedClasses"); - f_cachedClasses.setAccessible(true); - } - catch(Exception e) - { - throw new RuntimeException(e); - } - } - - @Override - public byte[] transform(String name, String tname, byte[] bytes) - { - if (bytes == null) return null; - for(IClassTransformer trans : delegatedTransformers) - bytes = trans.transform(name, tname, bytes); - return bytes; - } - - public static void addTransformer(String transformer, JarFile jar, File jarFile) - { - logger.debug("Adding CCTransformer: " + transformer); - try - { - byte[] bytes; - bytes = Launch.classLoader.getClassBytes(transformer); - - if(bytes == null) - { - String resourceName = transformer.replace('.', '/')+".class"; - ZipEntry entry = jar.getEntry(resourceName); - if(entry == null) - throw new Exception("Failed to add transformer: "+transformer+". Entry not found in jar file "+jarFile.getName()); - - bytes = readFully(jar.getInputStream(entry)); - } - - defineDependancies(bytes, jar, jarFile); - Class clazz = defineClass(transformer, bytes); - - if(!IClassTransformer.class.isAssignableFrom(clazz)) - throw new Exception("Failed to add transformer: "+transformer+" is not an instance of IClassTransformer"); - - IClassTransformer classTransformer; - try - { - classTransformer = (IClassTransformer) clazz.getDeclaredConstructor(File.class).newInstance(jarFile); - } - catch(NoSuchMethodException nsme) - { - classTransformer = (IClassTransformer) clazz.newInstance(); - } - delegatedTransformers.add(classTransformer); - } - catch(Exception e) - { - e.printStackTrace(); - } - } - - private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile) throws Exception - { - defineDependancies(bytes, jar, jarFile, new Stack()); - } - - private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile, Stack depStack) throws Exception - { - ClassReader reader = new ClassReader(bytes); - DependancyLister lister = new DependancyLister(Opcodes.ASM4); - reader.accept(lister, 0); - - depStack.push(reader.getClassName()); - - for(String dependancy : lister.getDependancies()) - { - if(depStack.contains(dependancy)) - continue; - - try - { - Launch.classLoader.loadClass(dependancy.replace('/', '.')); - } - catch(ClassNotFoundException cnfe) - { - ZipEntry entry = jar.getEntry(dependancy+".class"); - if(entry == null) - throw new Exception("Dependency "+dependancy+" not found in jar file "+jarFile.getName()); - - byte[] depbytes = readFully(jar.getInputStream(entry)); - defineDependancies(depbytes, jar, jarFile, depStack); - - logger.debug("Defining dependancy: "+dependancy); - - defineClass(dependancy.replace('/', '.'), depbytes); - } - } - - depStack.pop(); - } - - private static Class defineClass(String classname, byte[] bytes) throws Exception - { - Class clazz = (Class) m_defineClass.invoke(Launch.classLoader, classname, bytes, 0, bytes.length); - ((Map>)f_cachedClasses.get(Launch.classLoader)).put(classname, clazz); - return clazz; - } - - public static byte[] readFully(InputStream stream) throws IOException - { - ByteArrayOutputStream bos = new ByteArrayOutputStream(stream.available()); - int r; - while ((r = stream.read()) != -1) - { - bos.write(r); - } - - return bos.toByteArray(); - } -} diff --git a/src/codechicken/core/asm/InterfaceDependancyTransformer.java b/src/codechicken/core/asm/InterfaceDependancyTransformer.java deleted file mode 100644 index 7992829..0000000 --- a/src/codechicken/core/asm/InterfaceDependancyTransformer.java +++ /dev/null @@ -1,53 +0,0 @@ -package codechicken.core.asm; - -import java.util.Iterator; - -import codechicken.lib.asm.ASMInit; -import net.minecraft.launchwrapper.Launch; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.ClassNode; - -import codechicken.lib.asm.ASMHelper; -import codechicken.lib.asm.ObfMapping; - -import net.minecraft.launchwrapper.IClassTransformer; - -public class InterfaceDependancyTransformer implements IClassTransformer -{ - static { - ASMInit.init(); - } - - @Override - public byte[] transform(String name, String tname, byte[] bytes) { - if (bytes == null) return null; - ClassNode cnode = ASMHelper.createClassNode(bytes); - - boolean hasDependancyInterfaces = false; - if (cnode.visibleAnnotations != null) - for (AnnotationNode ann : cnode.visibleAnnotations) - if (ann.desc.equals(Type.getDescriptor(InterfaceDependancies.class))) { - hasDependancyInterfaces = true; - break; - } - - if (!hasDependancyInterfaces) - return bytes; - - hasDependancyInterfaces = false; - for (Iterator iterator = cnode.interfaces.iterator(); iterator.hasNext(); ) { - try { - Launch.classLoader.findClass(new ObfMapping(iterator.next()).toRuntime().javaClass()); - } catch (ClassNotFoundException cnfe) { - iterator.remove(); - hasDependancyInterfaces = true; - } - } - - if (!hasDependancyInterfaces) - return bytes; - - return ASMHelper.createBytes(cnode, 0); - } -} diff --git a/src/codechicken/core/asm/TweakTransformer.java b/src/codechicken/core/asm/TweakTransformer.java deleted file mode 100644 index 77484f5..0000000 --- a/src/codechicken/core/asm/TweakTransformer.java +++ /dev/null @@ -1,85 +0,0 @@ -package codechicken.core.asm; - -import codechicken.lib.asm.*; -import codechicken.lib.asm.ModularASMTransformer.MethodReplacer; -import codechicken.lib.asm.ModularASMTransformer.MethodTransformer; -import codechicken.lib.asm.ModularASMTransformer.MethodWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.JumpInsnNode; -import org.objectweb.asm.tree.LabelNode; -import org.objectweb.asm.tree.MethodNode; - -import codechicken.lib.config.ConfigTag; -import net.minecraft.launchwrapper.IClassTransformer; - -import java.util.Map; - -import static codechicken.lib.asm.InsnComparator.*; - -public class TweakTransformer implements IClassTransformer, Opcodes -{ - static { - ASMInit.init(); - } - - private static ModularASMTransformer transformer = new ModularASMTransformer(); - private static Map blocks = ASMReader.loadResource("/assets/codechickencore/asm/tweaks.asm"); - public static ConfigTag tweaks; - - public static void load() { - CodeChickenCoreModContainer.loadConfig(); - tweaks = CodeChickenCoreModContainer.config.getTag("tweaks") - .setComment("Various tweaks that can be applied to game mechanics.").useBraces(); - tweaks.removeTag("persistantLava"); - - if (tweaks.getTag("environmentallyFriendlyCreepers") - .setComment("If set to true, creepers will not destroy landscape. (A version of mobGriefing setting just for creepers)") - .getBooleanValue(false)) { - transformer.add(new MethodReplacer(new ObfMapping("net/minecraft/entity/monster/EntityCreeper", "func_146077_cc", "()V"), - blocks.get("d_environmentallyFriendlyCreepers"), blocks.get("environmentallyFriendlyCreepers"))); - } - - if (!tweaks.getTag("softLeafReplace") - .setComment("If set to false, leaves will only replace air when growing") - .getBooleanValue(false)) { - transformer.add(new MethodWriter(ACC_PUBLIC, new ObfMapping("net/minecraft/block/Block", "canBeReplacedByLeaves", "(Lnet/minecraft/world/IBlockAccess;III)Z"), blocks.get("softLeafReplace"))); - } - - if (tweaks.getTag("doFireTickOut") - .setComment("If set to true and doFireTick is disabled in the game rules, fire will still dissipate if it's not over a fire source") - .getBooleanValue(true)) { - transformer.add(new MethodTransformer(new ObfMapping("net/minecraft/block/BlockFire", "func_149674_a", "(Lnet/minecraft/world/World;IIILjava/util/Random;)V")) - { - @Override - public void transform(MethodNode mv) { - ASMBlock needle = blocks.get("n_doFireTick"); - ASMBlock inject = blocks.get("doFireTick"); - - ASMBlock key = needle.applyLabels(findOnce(mv.instructions, needle.list)); - LabelNode jlabel = key.get("LRET"); - mv.instructions.insertBefore(jlabel, new JumpInsnNode(GOTO, inject.get("LSKIP"))); - mv.instructions.insert(jlabel, inject.list.list); - } - }); - } - - if (tweaks.getTag("finiteWater") - .setComment("If set to true two adjacent water source blocks will not generate a third.") - .getBooleanValue(false)) { - transformer.add(new MethodTransformer(new ObfMapping("net/minecraft/block/BlockDynamicLiquid", "func_149674_a", "(Lnet/minecraft/world/World;IIILjava/util/Random;)V")) - { - @Override - public void transform(MethodNode mv) { - ASMBlock needle = blocks.get("finiteWater"); - ASMBlock key = needle.applyLabels(findOnce(mv.instructions, needle.list)); - mv.instructions.insertBefore(key.list.getFirst(), new JumpInsnNode(GOTO, key.get("LEND"))); - } - }); - } - } - - @Override - public byte[] transform(String name, String tname, byte[] bytes) { - return transformer.transform(name, bytes); - } -} diff --git a/src/codechicken/core/featurehack/RenderEntityRenderHook.java b/src/codechicken/core/featurehack/RenderEntityRenderHook.java deleted file mode 100644 index d72a2cb..0000000 --- a/src/codechicken/core/featurehack/RenderEntityRenderHook.java +++ /dev/null @@ -1,26 +0,0 @@ -package codechicken.core.featurehack; - -import org.lwjgl.opengl.GL11; - -import net.minecraft.entity.Entity; -import net.minecraft.util.ResourceLocation; -import net.minecraft.client.renderer.entity.Render; -import net.minecraftforge.client.MinecraftForgeClient; - -public class RenderEntityRenderHook extends Render -{ - @Override - public void doRender(Entity entity, double x, double y, double z, float f, float frame) - { - EntityRenderHook hook = (EntityRenderHook)entity; - GL11.glTranslated(x-hook.posX, y-hook.posY, z-hook.posZ); - ((EntityRenderHook)entity).callback.render(frame, MinecraftForgeClient.getRenderPass()); - GL11.glTranslated(hook.posX-x, hook.posY-y, hook.posZ-z); - } - - @Override - protected ResourceLocation getEntityTexture(Entity entity) - { - return null; - } -} diff --git a/src/codechicken/core/fluid/ExtendedFluidTank.java b/src/codechicken/core/fluid/ExtendedFluidTank.java deleted file mode 100644 index fc067f7..0000000 --- a/src/codechicken/core/fluid/ExtendedFluidTank.java +++ /dev/null @@ -1,117 +0,0 @@ -package codechicken.core.fluid; - -import net.minecraft.nbt.NBTTagCompound; -import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.fluids.FluidTankInfo; -import net.minecraftforge.fluids.IFluidTank; - -public class ExtendedFluidTank implements IFluidTank -{ - private FluidStack fluid; - private boolean changeType; - private int capacity; - - public ExtendedFluidTank(FluidStack type, int capacity) - { - if(type == null) { - type = FluidUtils.water; - changeType = true; - } - - fluid = FluidUtils.copy(type, 0); - this.capacity = capacity; - } - - public ExtendedFluidTank(int capacity) - { - this(null, capacity); - } - - @Override - public FluidStack getFluid() - { - return fluid.copy(); - } - - @Override - public int getCapacity() - { - return capacity; - } - - public boolean canAccept(FluidStack type) - { - return type == null || (fluid.amount == 0 && changeType) || fluid.isFluidEqual(type); - } - - @Override - public int fill(FluidStack resource, boolean doFill) - { - if(resource == null) - return 0; - - if(!canAccept(resource)) - return 0; - - int tofill = Math.min(getCapacity()-fluid.amount, resource.amount); - if(doFill && tofill > 0) - { - if(!fluid.isFluidEqual(resource)) - fluid = FluidUtils.copy(resource, fluid.amount+tofill); - else - fluid.amount+=tofill; - onLiquidChanged(); - } - - return tofill; - } - - @Override - public FluidStack drain(int maxDrain, boolean doDrain) - { - if(fluid.amount == 0 || maxDrain <= 0) - return null; - - int todrain = Math.min(maxDrain, fluid.amount); - if(doDrain && todrain > 0) - { - fluid.amount-=todrain; - onLiquidChanged(); - } - return FluidUtils.copy(fluid, todrain); - } - - public FluidStack drain(FluidStack resource, boolean doDrain) - { - if (resource == null || !resource.isFluidEqual(fluid)) - return null; - - return drain(resource.amount, doDrain); - } - - public void onLiquidChanged() - { - } - - public void fromTag(NBTTagCompound tag) - { - fluid = FluidUtils.read(tag); - } - - public NBTTagCompound toTag() - { - return FluidUtils.write(fluid, new NBTTagCompound()); - } - - @Override - public int getFluidAmount() - { - return fluid.amount; - } - - @Override - public FluidTankInfo getInfo() - { - return new FluidTankInfo(this); - } -} diff --git a/src/codechicken/core/gui/GuiCCTextField.java b/src/codechicken/core/gui/GuiCCTextField.java deleted file mode 100644 index 84463b4..0000000 --- a/src/codechicken/core/gui/GuiCCTextField.java +++ /dev/null @@ -1,189 +0,0 @@ -package codechicken.core.gui; - -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.util.ChatAllowedCharacters; - -import org.lwjgl.input.Keyboard; - -public class GuiCCTextField extends GuiWidget -{ - private String text; - private boolean isFocused; - private boolean isEnabled; - - public int maxStringLength; - public int cursorCounter; - public String actionCommand; - - private String allowedCharacters; - - public GuiCCTextField(int x, int y, int width, int height, String text) - { - super(x, y, width, height); - isFocused = false; - isEnabled = true; - this.text = text; - } - - public GuiCCTextField setActionCommand(String s) - { - actionCommand = s; - return this; - } - - public void setText(String s) - { - if(s.equals(text)) - return; - - String oldText = text; - text = s; - onTextChanged(oldText); - } - - public void onTextChanged(String oldText) - { - } - - public final String getText() - { - return text; - } - - public final boolean isEnabled() - { - return isEnabled; - } - - public void setEnabled(boolean b) - { - isEnabled = b; - if(!isEnabled && isFocused) - setFocused(false); - } - - public final boolean isFocused() - { - return isFocused; - } - - @Override - public void update() - { - cursorCounter++; - } - - @Override - public void keyTyped(char c, int keycode) - { - if(!isEnabled || !isFocused) - return; - - /*if(c == '\t')//tab - { - parentGuiScreen.selectNextField(); - }*/ - if(c == '\026')//paste - { - String s = GuiScreen.getClipboardString(); - if(s == null || s.equals("")) - return; - - for(int i = 0; i < s.length(); i++) - { - if(text.length() == maxStringLength) - return; - - char tc = s.charAt(i); - if(canAddChar(tc)) - setText(text + tc); - } - } - if(keycode == Keyboard.KEY_RETURN) - { - setFocused(false); - sendAction(actionCommand, getText()); - } - - if(keycode == Keyboard.KEY_BACK && text.length() > 0) - setText(text.substring(0, text.length() - 1)); - - if((text.length() < maxStringLength || maxStringLength == 0) && canAddChar(c)) - setText(text + c); - } - - public boolean canAddChar(char c) - { - return allowedCharacters == null ? ChatAllowedCharacters.isAllowedCharacter(c) : allowedCharacters.indexOf(c) >= 0; - } - - @Override - public void mouseClicked(int x, int y, int button) - { - if(isEnabled && pointInside(x, y)) - { - setFocused(true); - if(button == 1) - setText(""); - } - else - setFocused(false); - } - - public void setFocused(boolean focus) - { - if(focus == isFocused) - return; - isFocused = focus; - onFocusChanged(); - } - - public void onFocusChanged() - { - if(isFocused) - cursorCounter = 0; - } - - @Override - public void draw(int i, int j, float f) - { - drawBackground(); - drawText(); - } - - public void drawBackground() - { - drawRect(x - 1, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0); - drawRect(x, y, x + width, y + height, 0xff000000); - } - - public String getDrawText() - { - String s = getText(); - if(isEnabled && isFocused && (cursorCounter / 6) % 2 == 0) - s+="_"; - return s; - } - - public void drawText() - { - drawString(fontRenderer, getDrawText(), x + 4, y + height/2 - 4, getTextColour()); - } - - public int getTextColour() - { - return isEnabled ? 0xe0e0e0 : 0x707070; - } - - public GuiCCTextField setMaxStringLength(int i) - { - maxStringLength = i; - return this; - } - - public GuiCCTextField setAllowedCharacters(String s) - { - allowedCharacters = s; - return this; - } -} diff --git a/src/codechicken/core/launch/CodeChickenCorePlugin.java b/src/codechicken/core/launch/CodeChickenCorePlugin.java deleted file mode 100644 index 61aeb82..0000000 --- a/src/codechicken/core/launch/CodeChickenCorePlugin.java +++ /dev/null @@ -1,167 +0,0 @@ -package codechicken.core.launch; - -import java.awt.Desktop; -import java.io.File; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.util.List; -import java.util.Map; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -import javax.swing.JEditorPane; -import javax.swing.JOptionPane; -import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; - -import codechicken.core.asm.*; -import cpw.mods.fml.relauncher.CoreModManager; - -import cpw.mods.fml.common.versioning.DefaultArtifactVersion; -import cpw.mods.fml.common.versioning.VersionParser; -import cpw.mods.fml.relauncher.FMLInjectionData; -import cpw.mods.fml.relauncher.IFMLCallHook; -import cpw.mods.fml.relauncher.IFMLLoadingPlugin; -import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -@TransformerExclusions(value = {"codechicken.core.asm", "codechicken.obfuscator"}) -public class CodeChickenCorePlugin implements IFMLLoadingPlugin, IFMLCallHook -{ - public static final String mcVersion = "[1.7.10]"; - public static final String version = "${mod_version}"; - - public static File minecraftDir; - public static String currentMcVersion; - public static Logger logger = LogManager.getLogger("CodeChickenCore"); - - public CodeChickenCorePlugin() { - if (minecraftDir != null) - return;//get called twice, once for IFMLCallHook - - minecraftDir = (File) FMLInjectionData.data()[6]; - currentMcVersion = (String) FMLInjectionData.data()[4]; - - DepLoader.load(); - injectDeobfPlugin(); - } - - private void injectDeobfPlugin() { - try { - Class wrapperClass = Class.forName("cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper"); - Constructor wrapperConstructor = wrapperClass.getConstructor(String.class, IFMLLoadingPlugin.class, File.class, Integer.TYPE, String[].class); - Field f_loadPlugins = CoreModManager.class.getDeclaredField("loadPlugins"); - wrapperConstructor.setAccessible(true); - f_loadPlugins.setAccessible(true); - ((List)f_loadPlugins.get(null)).add(2, wrapperConstructor.newInstance("CCCDeobfPlugin", new MCPDeobfuscationTransformer.LoadPlugin(), null, 0, new String[0])); - } catch (Exception e) { - logger.error("Failed to inject MCPDeobfuscation Transformer", e); - } - } - - public static void versionCheck(String reqVersion, String mod) { - String mcVersion = (String) FMLInjectionData.data()[4]; - if (!VersionParser.parseRange(reqVersion).containsVersion(new DefaultArtifactVersion(mcVersion))) { - String err = "This version of " + mod + " does not support minecraft version " + mcVersion; - logger.error(err); - - JEditorPane ep = new JEditorPane("text/html", - "" + - err + - "
Remove it from your coremods folder and check here for updates" + - ""); - - ep.setEditable(false); - ep.setOpaque(false); - ep.addHyperlinkListener(new HyperlinkListener() - { - @Override - public void hyperlinkUpdate(HyperlinkEvent event) { - try { - if (event.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) - Desktop.getDesktop().browse(event.getURL().toURI()); - } catch (Exception ignored) {} - } - }); - - JOptionPane.showMessageDialog(null, ep, "Fatal error", JOptionPane.ERROR_MESSAGE); - System.exit(1); - } - } - - @Override - public String[] getASMTransformerClass() { - versionCheck(mcVersion, "CodeChickenCore"); - return new String[]{ - "codechicken.lib.asm.ClassHeirachyManager", - "codechicken.core.asm.InterfaceDependancyTransformer", - "codechicken.core.asm.TweakTransformer", - "codechicken.core.asm.DelegatedTransformer", - "codechicken.core.asm.DefaultImplementationTransformer"}; - } - - @Override - public String getAccessTransformerClass() { - return "codechicken.core.asm.CodeChickenAccessTransformer"; - } - - @Override - public String getModContainerClass() { - return "codechicken.core.asm.CodeChickenCoreModContainer"; - } - - @Override - public String getSetupClass() { - return getClass().getName(); - } - - @Override - public void injectData(Map data) { - } - - @Override - public Void call() { - CodeChickenCoreModContainer.loadConfig(); - TweakTransformer.load(); - scanCodeChickenMods(); - - return null; - } - - private void scanCodeChickenMods() { - File modsDir = new File(minecraftDir, "mods"); - for (File file : modsDir.listFiles()) - scanMod(file); - File versionModsDir = new File(minecraftDir, "mods/" + currentMcVersion); - if (versionModsDir.exists()) - for (File file : versionModsDir.listFiles()) - scanMod(file); - } - - private void scanMod(File file) { - if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) - return; - - try { - JarFile jar = new JarFile(file); - try { - Manifest manifest = jar.getManifest(); - if (manifest == null) - return; - Attributes attr = manifest.getMainAttributes(); - if (attr == null) - return; - - String transformer = attr.getValue("CCTransformer"); - if (transformer != null) - DelegatedTransformer.addTransformer(transformer, jar, file); - } finally { - jar.close(); - } - } catch (Exception e) { - logger.error("CodeChickenCore: Failed to read jar file: " + file.getName(), e); - } - } -} diff --git a/src/codechicken/core/launch/DepLoader.java b/src/codechicken/core/launch/DepLoader.java deleted file mode 100644 index 0032ed5..0000000 --- a/src/codechicken/core/launch/DepLoader.java +++ /dev/null @@ -1,618 +0,0 @@ -package codechicken.core.launch; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import cpw.mods.fml.common.versioning.ComparableVersion; -import cpw.mods.fml.relauncher.FMLInjectionData; -import cpw.mods.fml.relauncher.FMLLaunchHandler; -import cpw.mods.fml.relauncher.IFMLCallHook; -import cpw.mods.fml.relauncher.IFMLLoadingPlugin; -import net.minecraft.launchwrapper.LaunchClassLoader; -import sun.misc.URLClassPath; -import sun.net.util.URLUtil; - -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; -import java.awt.*; -import java.awt.Dialog.ModalityType; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.*; -import java.lang.reflect.Field; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLConnection; -import java.nio.ByteBuffer; -import java.util.*; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -/** - * For autodownloading stuff. - * This is really unoriginal, mostly ripped off FML, credits to cpw. - */ -public class DepLoader implements IFMLLoadingPlugin, IFMLCallHook { - private static ByteBuffer downloadBuffer = ByteBuffer.allocateDirect(1 << 23); - private static final String owner = "CB's DepLoader"; - private static DepLoadInst inst; - - public interface IDownloadDisplay { - void resetProgress(int sizeGuess); - - void setPokeThread(Thread currentThread); - - void updateProgress(int fullLength); - - boolean shouldStopIt(); - - void updateProgressString(String string, Object... data); - - Object makeDialog(); - - void showErrorDialog(String name, String url); - } - - @SuppressWarnings("serial") - public static class Downloader extends JOptionPane implements IDownloadDisplay { - private JDialog container; - private JLabel currentActivity; - private JProgressBar progress; - boolean stopIt; - Thread pokeThread; - - private Box makeProgressPanel() { - Box box = Box.createVerticalBox(); - box.add(Box.createRigidArea(new Dimension(0, 10))); - JLabel welcomeLabel = new JLabel("" + owner + " is setting up your minecraft environment"); - box.add(welcomeLabel); - welcomeLabel.setAlignmentY(LEFT_ALIGNMENT); - welcomeLabel = new JLabel("Please wait, " + owner + " has some tasks to do before you can play"); - welcomeLabel.setAlignmentY(LEFT_ALIGNMENT); - box.add(welcomeLabel); - box.add(Box.createRigidArea(new Dimension(0, 10))); - currentActivity = new JLabel("Currently doing ..."); - box.add(currentActivity); - box.add(Box.createRigidArea(new Dimension(0, 10))); - progress = new JProgressBar(0, 100); - progress.setStringPainted(true); - box.add(progress); - box.add(Box.createRigidArea(new Dimension(0, 30))); - return box; - } - - @Override - public JDialog makeDialog() { - if (container != null) - return container; - - setMessageType(JOptionPane.INFORMATION_MESSAGE); - setMessage(makeProgressPanel()); - setOptions(new Object[]{"Stop"}); - addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getSource() == Downloader.this && evt.getPropertyName() == VALUE_PROPERTY) { - requestClose("This will stop minecraft from launching\nAre you sure you want to do this?"); - } - } - }); - container = new JDialog(null, "Hello", ModalityType.MODELESS); - container.setResizable(false); - container.setLocationRelativeTo(null); - container.add(this); - this.updateUI(); - container.pack(); - container.setMinimumSize(container.getPreferredSize()); - container.setVisible(true); - container.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - container.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - requestClose("Closing this window will stop minecraft from launching\nAre you sure you wish to do this?"); - } - }); - return container; - } - - protected void requestClose(String message) { - int shouldClose = JOptionPane.showConfirmDialog(container, message, "Are you sure you want to stop?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); - if (shouldClose == JOptionPane.YES_OPTION) - container.dispose(); - - stopIt = true; - if (pokeThread != null) - pokeThread.interrupt(); - } - - @Override - public void updateProgressString(String progressUpdate, Object... data) { - //FMLLog.finest(progressUpdate, data); - if (currentActivity != null) - currentActivity.setText(String.format(progressUpdate, data)); - } - - @Override - public void resetProgress(int sizeGuess) { - if (progress != null) - progress.getModel().setRangeProperties(0, 0, 0, sizeGuess, false); - } - - @Override - public void updateProgress(int fullLength) { - if (progress != null) - progress.getModel().setValue(fullLength); - } - - @Override - public void setPokeThread(Thread currentThread) { - this.pokeThread = currentThread; - } - - @Override - public boolean shouldStopIt() { - return stopIt; - } - - @Override - public void showErrorDialog(String name, String url) { - JEditorPane ep = new JEditorPane("text/html", - "" + - owner + " was unable to download required library " + name + - "
Check your internet connection and try restarting or download it manually from" + - "
" + url + " and put it in your mods folder" + - ""); - - ep.setEditable(false); - ep.setOpaque(false); - ep.addHyperlinkListener(new HyperlinkListener() { - @Override - public void hyperlinkUpdate(HyperlinkEvent event) { - try { - if (event.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) - Desktop.getDesktop().browse(event.getURL().toURI()); - } catch (Exception e) { - } - } - }); - - JOptionPane.showMessageDialog(null, ep, "A download error has occured", JOptionPane.ERROR_MESSAGE); - } - } - - public static class DummyDownloader implements IDownloadDisplay { - @Override - public void resetProgress(int sizeGuess) { - } - - @Override - public void setPokeThread(Thread currentThread) { - } - - @Override - public void updateProgress(int fullLength) { - } - - @Override - public boolean shouldStopIt() { - return false; - } - - @Override - public void updateProgressString(String string, Object... data) { - } - - @Override - public Object makeDialog() { - return null; - } - - @Override - public void showErrorDialog(String name, String url) { - } - } - - public static class VersionedFile - { - public final Pattern pattern; - public final String filename; - public final ComparableVersion version; - public final String name; - - public VersionedFile(String filename, Pattern pattern) { - this.pattern = pattern; - this.filename = filename; - Matcher m = pattern.matcher(filename); - if(m.matches()) { - name = m.group(1); - version = new ComparableVersion(m.group(2)); - } - else { - name = null; - version = null; - } - } - - public boolean matches() { - return name != null; - } - } - - public static class Dependency - { - public String url; - public VersionedFile file; - - public String existing; - /** - * Flag set to add this dep to the classpath immediately because it is required for a coremod. - */ - public boolean coreLib; - - public Dependency(String url, VersionedFile file, boolean coreLib) { - this.url = url; - this.file = file; - this.coreLib = coreLib; - } - } - - public static class DepLoadInst { - private File modsDir; - private File v_modsDir; - private IDownloadDisplay downloadMonitor; - private JDialog popupWindow; - - private Map depMap = new HashMap(); - private HashSet depSet = new HashSet(); - - public DepLoadInst() { - String mcVer = (String) FMLInjectionData.data()[4]; - File mcDir = (File) FMLInjectionData.data()[6]; - - modsDir = new File(mcDir, "mods"); - v_modsDir = new File(mcDir, "mods/" + mcVer); - if (!v_modsDir.exists()) - v_modsDir.mkdirs(); - } - - private void addClasspath(String name) { - try { - ((LaunchClassLoader) DepLoader.class.getClassLoader()).addURL(new File(v_modsDir, name).toURI().toURL()); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - private void deleteMod(File mod) { - if (mod.delete()) - return; - - try { - ClassLoader cl = DepLoader.class.getClassLoader(); - URL url = mod.toURI().toURL(); - Field f_ucp = URLClassLoader.class.getDeclaredField("ucp"); - Field f_loaders = URLClassPath.class.getDeclaredField("loaders"); - Field f_lmap = URLClassPath.class.getDeclaredField("lmap"); - f_ucp.setAccessible(true); - f_loaders.setAccessible(true); - f_lmap.setAccessible(true); - - URLClassPath ucp = (URLClassPath) f_ucp.get(cl); - Closeable loader = ((Map) f_lmap.get(ucp)).remove(URLUtil.urlNoFragString(url)); - if (loader != null) { - loader.close(); - ((List) f_loaders.get(ucp)).remove(loader); - } - } catch (Exception e) { - e.printStackTrace(); - } - - if (!mod.delete()) { - mod.deleteOnExit(); - String msg = owner + " was unable to delete file " + mod.getPath() + " the game will now try to delete it on exit. If this dialog appears again, delete it manually."; - System.err.println(msg); - if (!GraphicsEnvironment.isHeadless()) - JOptionPane.showMessageDialog(null, msg, "An update error has occured", JOptionPane.ERROR_MESSAGE); - - System.exit(1); - } - } - - private void download(Dependency dep) { - popupWindow = (JDialog) downloadMonitor.makeDialog(); - File libFile = new File(v_modsDir, dep.file.filename); - try { - URL libDownload = new URL(dep.url + '/' + dep.file.filename); - downloadMonitor.updateProgressString("Downloading file %s", libDownload.toString()); - System.out.format("Downloading file %s\n", libDownload.toString()); - URLConnection connection = libDownload.openConnection(); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - connection.setRequestProperty("User-Agent", "" + owner + " Downloader"); - int sizeGuess = connection.getContentLength(); - download(connection.getInputStream(), sizeGuess, libFile); - downloadMonitor.updateProgressString("Download complete"); - System.out.println("Download complete"); - - scanDepInfo(libFile); - } catch (Exception e) { - libFile.delete(); - if (downloadMonitor.shouldStopIt()) { - System.err.println("You have stopped the downloading operation before it could complete"); - System.exit(1); - return; - } - downloadMonitor.showErrorDialog(dep.file.filename, dep.url + '/' + dep.file.filename); - throw new RuntimeException("A download error occured", e); - } - } - - private void download(InputStream is, int sizeGuess, File target) throws Exception { - if (sizeGuess > downloadBuffer.capacity()) - throw new Exception(String.format("The file %s is too large to be downloaded by " + owner + " - the download is invalid", target.getName())); - - downloadBuffer.clear(); - - int bytesRead, fullLength = 0; - - downloadMonitor.resetProgress(sizeGuess); - try { - downloadMonitor.setPokeThread(Thread.currentThread()); - byte[] smallBuffer = new byte[1024]; - while ((bytesRead = is.read(smallBuffer)) >= 0) { - downloadBuffer.put(smallBuffer, 0, bytesRead); - fullLength += bytesRead; - if (downloadMonitor.shouldStopIt()) { - break; - } - downloadMonitor.updateProgress(fullLength); - } - is.close(); - downloadMonitor.setPokeThread(null); - downloadBuffer.limit(fullLength); - downloadBuffer.position(0); - } catch (InterruptedIOException e) { - // We were interrupted by the stop button. We're stopping now.. clear interruption flag. - Thread.interrupted(); - throw new Exception("Stop"); - } catch (IOException e) { - throw e; - } - - try { - /*String cksum = generateChecksum(downloadBuffer); - if (cksum.equals(validationHash)) - {*/ - if (!target.exists()) - target.createNewFile(); - - - downloadBuffer.position(0); - FileOutputStream fos = new FileOutputStream(target); - fos.getChannel().write(downloadBuffer); - fos.close(); - /*} - else - { - throw new RuntimeException(String.format("The downloaded file %s has an invalid checksum %s (expecting %s). The download did not succeed correctly and the file has been deleted. Please try launching again.", target.getName(), cksum, validationHash)); - }*/ - } catch (Exception e) { - throw e; - } - } - - private String checkExisting(Dependency dep) { - for (File f : modsDir.listFiles()) { - VersionedFile vfile = new VersionedFile(f.getName(), dep.file.pattern); - if (!vfile.matches() || !vfile.name.equals(dep.file.name)) - continue; - - if (f.renameTo(new File(v_modsDir, f.getName()))) - continue; - - deleteMod(f); - } - - for (File f : v_modsDir.listFiles()) { - VersionedFile vfile = new VersionedFile(f.getName(), dep.file.pattern); - if (!vfile.matches() || !vfile.name.equals(dep.file.name)) - continue; - - int cmp = vfile.version.compareTo(dep.file.version); - if (cmp < 0) { - System.out.println("Deleted old version " + f.getName()); - deleteMod(f); - return null; - } - if (cmp > 0) { - System.err.println("Warning: version of " + dep.file.name + ", " + vfile.version + " is newer than request " + dep.file.version); - return f.getName(); - } - return f.getName();//found dependency - } - return null; - } - - public void load() { - scanDepInfos(); - if (depMap.isEmpty()) - return; - - loadDeps(); - activateDeps(); - } - - private void activateDeps() { - for (Dependency dep : depMap.values()) - if (dep.coreLib) - addClasspath(dep.existing); - } - - private void loadDeps() { - downloadMonitor = FMLLaunchHandler.side().isClient() ? new Downloader() : new DummyDownloader(); - try { - while (!depSet.isEmpty()) { - Iterator it = depSet.iterator(); - Dependency dep = depMap.get(it.next()); - it.remove(); - load(dep); - } - } finally { - if (popupWindow != null) { - popupWindow.setVisible(false); - popupWindow.dispose(); - } - } - } - - private void load(Dependency dep) { - dep.existing = checkExisting(dep); - if (dep.existing == null)//download dep - { - download(dep); - dep.existing = dep.file.filename; - } - } - - private List modFiles() { - List list = new LinkedList(); - list.addAll(Arrays.asList(modsDir.listFiles())); - list.addAll(Arrays.asList(v_modsDir.listFiles())); - return list; - } - - private void scanDepInfos() { - for (File file : modFiles()) { - if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) - continue; - - scanDepInfo(file); - } - } - - private void scanDepInfo(File file) { - try { - ZipFile zip = new ZipFile(file); - ZipEntry e = zip.getEntry("dependancies.info"); - if (e == null) e = zip.getEntry("dependencies.info"); - if (e != null) - loadJSon(zip.getInputStream(e)); - zip.close(); - } catch (Exception e) { - System.err.println("Failed to load dependencies.info from " + file.getName() + " as JSON"); - e.printStackTrace(); - } - } - - private void loadJSon(InputStream input) throws IOException { - InputStreamReader reader = new InputStreamReader(input); - JsonElement root = new JsonParser().parse(reader); - if (root.isJsonArray()) - loadJSonArr(root); - else - loadJson(root.getAsJsonObject()); - reader.close(); - } - - private void loadJSonArr(JsonElement root) throws IOException { - for (JsonElement node : root.getAsJsonArray()) - loadJson(node.getAsJsonObject()); - } - - private void loadJson(JsonObject node) throws IOException { - boolean obfuscated = ((LaunchClassLoader) DepLoader.class.getClassLoader()) - .getClassBytes("net.minecraft.world.World") == null; - - String testClass = node.get("class").getAsString(); - if (DepLoader.class.getResource("/" + testClass.replace('.', '/') + ".class") != null) - return; - - String repo = node.get("repo").getAsString(); - String filename = node.get("file").getAsString(); - if (!obfuscated && node.has("dev")) - filename = node.get("dev").getAsString(); - - boolean coreLib = node.has("coreLib") && node.get("coreLib").getAsBoolean(); - - Pattern pattern = null; - try { - if(node.has("pattern")) - pattern = Pattern.compile(node.get("pattern").getAsString()); - } catch (PatternSyntaxException e) { - System.err.println("Invalid filename pattern: "+node.get("pattern")); - e.printStackTrace(); - } - if(pattern == null) - pattern = Pattern.compile("(\\w+).*?([\\d\\.]+)[-\\w]*\\.[^\\d]+"); - - VersionedFile file = new VersionedFile(filename, pattern); - if (!file.matches()) - throw new RuntimeException("Invalid filename format for dependency: " + filename); - - addDep(new Dependency(repo, file, coreLib)); - } - - private void addDep(Dependency newDep) { - if (mergeNew(depMap.get(newDep.file.name), newDep)) { - depMap.put(newDep.file.name, newDep); - depSet.add(newDep.file.name); - } - } - - private boolean mergeNew(Dependency oldDep, Dependency newDep) { - if (oldDep == null) - return true; - - Dependency newest = newDep.file.version.compareTo(oldDep.file.version) > 0 ? newDep : oldDep; - newest.coreLib = newDep.coreLib || oldDep.coreLib; - - return newest == newDep; - } - } - - public static void load() { - if (inst == null) { - inst = new DepLoadInst(); - inst.load(); - } - } - - @Override - public String[] getASMTransformerClass() { - return null; - } - - @Override - public String getModContainerClass() { - return null; - } - - @Override - public String getSetupClass() { - return getClass().getName(); - } - - @Override - public void injectData(Map data) { - } - - @Override - public Void call() { - load(); - - return null; - } - - @Override - public String getAccessTransformerClass() { - return null; - } -} diff --git a/src/codechicken/obfuscator/ConstantObfuscator.java b/src/codechicken/obfuscator/ConstantObfuscator.java deleted file mode 100644 index bb090c5..0000000 --- a/src/codechicken/obfuscator/ConstantObfuscator.java +++ /dev/null @@ -1,83 +0,0 @@ -package codechicken.obfuscator; - -import java.util.LinkedList; -import java.util.List; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.LdcInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; - -import codechicken.lib.asm.ObfMapping; - -import static org.objectweb.asm.tree.AbstractInsnNode.*; - -public class ConstantObfuscator implements Opcodes -{ - public ObfRemapper obf; - public List descCalls = new LinkedList(); - public List classCalls = new LinkedList(); - - public ConstantObfuscator(ObfRemapper obf, String[] a_classCalls, String[] a_descCalls) - { - this.obf = obf; - for(String callDesc : a_classCalls) - classCalls.add(ObfMapping.fromDesc(callDesc)); - - for(String callDesc : a_descCalls) - descCalls.add(ObfMapping.fromDesc(callDesc)); - } - - public void transform(ClassNode cnode) - { - for(MethodNode method : cnode.methods) - for(AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) - obfuscateInsnSeq(insn); - } - - private void obfuscateInsnSeq(AbstractInsnNode insn) - { - if(matchesClass(insn)) - { - LdcInsnNode node1 = (LdcInsnNode) insn; - node1.cst = obf.map((String) node1.cst); - } - if(matchesDesc(insn)) - { - LdcInsnNode node1 = (LdcInsnNode) insn; - LdcInsnNode node2 = (LdcInsnNode) node1.getNext(); - LdcInsnNode node3 = (LdcInsnNode) node2.getNext(); - ObfMapping mapping = new ObfMapping((String) node1.cst, (String) node2.cst, (String) node3.cst).map(obf); - node1.cst = mapping.s_owner; - node2.cst = mapping.s_name; - node3.cst = mapping.s_desc; - } - } - - private boolean matchesClass(AbstractInsnNode insn) - { - if(insn.getType() != LDC_INSN) return false; - insn = insn.getNext(); - if(insn == null || insn.getType() != METHOD_INSN) return false; - for(ObfMapping m : classCalls) - if(m.matches((MethodInsnNode) insn)) - return true; - return false; - } - - private boolean matchesDesc(AbstractInsnNode insn) - { - if(insn.getType() != LDC_INSN) return false; - insn = insn.getNext(); - if(insn == null || insn.getType() != LDC_INSN) return false; - insn = insn.getNext(); - if(insn == null || insn.getType() != LDC_INSN) return false; - insn = insn.getNext(); - if(insn == null || insn.getType() != METHOD_INSN) return false; - for(ObfMapping m : descCalls) - if(m.matches((MethodInsnNode) insn)) - return true; - return false; - } -} diff --git a/src/codechicken/obfuscator/ObfRemapper.java b/src/codechicken/obfuscator/ObfRemapper.java deleted file mode 100644 index 3cc70e7..0000000 --- a/src/codechicken/obfuscator/ObfRemapper.java +++ /dev/null @@ -1,91 +0,0 @@ -package codechicken.obfuscator; - -import org.objectweb.asm.commons.Remapper; - -import codechicken.obfuscator.ObfuscationMap.ObfuscationEntry; - -public class ObfRemapper extends Remapper -{ - public final ObfuscationMap obf; - public ObfDirection dir; - - public ObfRemapper(ObfuscationMap obf, ObfDirection dir) - { - this.obf = obf; - this.dir = dir; - } - - @Override - public String map(String name) - { - if(name.indexOf('$') >= 0) - return map(name.substring(0, name.indexOf('$')))+name.substring(name.indexOf('$')); - - ObfuscationEntry map; - if(dir.obfuscate) - map = obf.lookupMcpClass(name); - else - map = obf.lookupObfClass(name); - - if(map != null) - return dir.obfuscate(map).s_owner; - - return name; - } - - @Override - public String mapFieldName(String owner, String name, String desc) - { - ObfuscationEntry map; - if(dir.obfuscate) - map = obf.lookupMcpField(owner, name); - else - map = obf.lookupObfField(owner, name); - - if(map == null) - map = obf.lookupSrgField(owner, name); - - if(map != null) - return dir.obfuscate(map).s_name; - - return name; - } - - @Override - public String mapMethodName(String owner, String name, String desc) - { - if(owner.length() == 0 || owner.charAt(0) == '[') - return name; - - ObfuscationEntry map; - if(dir.obfuscate) - map = obf.lookupMcpMethod(owner, name, desc); - else - map = obf.lookupObfMethod(owner, name, desc); - - if(map == null) - map = obf.lookupSrg(name); - - if(map != null) - return dir.obfuscate(map).s_name; - - return name; - } - - @Override - public Object mapValue(Object cst) - { - if(cst instanceof String) - { - if(dir.srg_cst) - { - ObfuscationEntry map = obf.lookupSrg((String) cst); - if(map != null) - return dir.obfuscate(map).s_name; - } - return cst; - } - - return super.mapValue(cst); - } -} diff --git a/src/codechicken/obfuscator/ObfuscationRun.java b/src/codechicken/obfuscator/ObfuscationRun.java deleted file mode 100644 index 67e2178..0000000 --- a/src/codechicken/obfuscator/ObfuscationRun.java +++ /dev/null @@ -1,253 +0,0 @@ -package codechicken.obfuscator; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.PrintStream; -import java.text.DecimalFormat; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.commons.RemappingClassAdapter; -import org.objectweb.asm.tree.ClassNode; - -import com.google.common.base.Function; - -public class ObfuscationRun implements ILogStreams -{ - public final ObfDirection obfDir; - public final ObfuscationMap obf; - public final ObfRemapper obfMapper; - public final ConstantObfuscator cstMappper; - - public File[] mappings; - public Map config; - - private PrintStream out = System.out; - private PrintStream err = System.err; - private PrintStream quietStream = new PrintStream(DummyOutputStream.instance); - - private boolean verbose; - private boolean quiet; - - public boolean clean; - private long startTime; - private boolean finished; - - public ObfuscationRun(boolean obfuscate, File[] mappings, Map config) - { - obfDir = new ObfDirection().setObfuscate(obfuscate); - this.mappings = mappings; - this.config = config; - - obf = new ObfuscationMap().setLog(this); - obfMapper = new ObfRemapper(obf, obfDir); - cstMappper = new ConstantObfuscator(obfMapper, - config.get("classConstantCalls").split(","), - config.get("descConstantCalls").split(",")); - } - - public ObfuscationRun setClean() - { - clean = true; - return this; - } - - public ObfuscationRun setVerbose() - { - verbose = true; - return this; - } - - public ObfuscationRun setQuiet() - { - quiet = true; - return this; - } - - public ObfuscationRun setOut(PrintStream p) - { - out = p; - return this; - } - - public PrintStream out() - { - return quiet ? quietStream : out; - } - - public PrintStream fine() - { - return verbose ? out : quietStream; - } - - public ObfuscationRun setErr(PrintStream p) - { - err = p; - return this; - } - - public PrintStream err() - { - return quiet ? quietStream : err; - } - - public ObfuscationRun setSearge() - { - obfDir.setSearge(true); - return this; - } - - public ObfuscationRun setSeargeConstants() - { - obfDir.setSeargeConstants(true); - return this; - } - - public void start() - { - startTime = System.currentTimeMillis(); - } - - public static Map fillDefaults(Map config) - { - if(!config.containsKey("excludedPackages")) - config.put("excludedPackages", "java/;sun/;javax/;scala/;" + - "argo/;org/lwjgl/;org/objectweb/;org/bouncycastle/;com/google/"); - if(!config.containsKey("ignore")) - config.put("ignore", "."); - if(!config.containsKey("classConstantCalls")) - config.put("classConstantCalls", - "codechicken/lib/asm/ObfMapping.(Ljava/lang/String;)V," + - "codechicken/lib/asm/ObfMapping.subclass(Ljava/lang/String;)Lcodechicken/lib/asm/ObfMapping;," + - "codechicken/lib/asm/ObfMapping.(Lcodechicken/lib/asm/ObfMapping;Ljava/lang/String;)V"); - if(!config.containsKey("descConstantCalls")) - config.put("descConstantCalls", - "codechicken/lib/asm/ObfMapping.(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + - "org/objectweb/asm/MethodVisitor.visitFieldInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + - "org/objectweb/asm/tree/MethodNode.visitFieldInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + - "org/objectweb/asm/MethodVisitor.visitMethodInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + - "org/objectweb/asm/tree/MethodNode.visitMethodInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + - "org/objectweb/asm/tree/MethodInsnNode.(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + - "org/objectweb/asm/tree/FieldInsnNode.(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); - - return config; - } - - public static void processLines(File file, Function function) - { - try - { - BufferedReader reader = new BufferedReader(new FileReader(file)); - String line; - while((line = reader.readLine()) != null) - function.apply(line); - reader.close(); - } - catch(Exception e) - { - throw new RuntimeException(e); - } - } - - public static void processFiles(File dir, Function function, boolean recursive) - { - for(File file : dir.listFiles()) - if(file.isDirectory() && recursive) - processFiles(file, function, recursive); - else - function.apply(file); - } - - public static void deleteDir(File directory, boolean remove) - { - if(!directory.exists()) - { - if(!remove)directory.mkdirs(); - return; - } - for(File file : directory.listFiles()) - { - if(file.isDirectory()) - { - deleteDir(file, true); - } - else - { - if(!file.delete()) - throw new RuntimeException("Delete Failed: "+file); - } - } - if(remove) - { - if(!directory.delete()) - throw new RuntimeException("Delete Failed: "+directory); - } - } - - public static File[] parseConfDir(File confDir) { - File srgDir = new File(confDir, "conf"); - if(!srgDir.exists()) - srgDir = confDir; - - File srgs = new File(srgDir, "packaged.srg"); - if(!srgs.exists()) - srgs = new File(srgDir, "joined.srg"); - if(!srgs.exists()) - throw new RuntimeException("Could not find packaged.srg or joined.srg"); - - File mapDir = new File(confDir, "mappings"); - if(!mapDir.exists()) - mapDir = confDir; - - File methods = new File(mapDir, "methods.csv"); - if(!methods.exists()) - throw new RuntimeException("Could not find methods.csv"); - File fields = new File(mapDir, "fields.csv"); - if(!fields.exists()) - throw new RuntimeException("Could not find fields.csv"); - - return new File[]{srgs, methods, fields}; - } - - public long startTime() - { - return startTime; - } - - public void remap(ClassNode cnode, ClassVisitor cv) - { - cstMappper.transform(cnode); - cnode.accept(new RemappingClassAdapter(cv, obfMapper)); - } - - public static List getParents(ClassNode cnode) - { - List parents = new LinkedList(); - if(cnode.superName != null) - parents.add(cnode.superName); - - for(String s_interface : cnode.interfaces) - parents.add(s_interface); - - return parents; - } - - public void finish(boolean errored) - { - long millis = System.currentTimeMillis()-startTime; - out().println((errored ? "Errored after" : "Done in ")+new DecimalFormat("0.00").format(millis/1000D)+"s"); - finished = true; - } - - public boolean finished() - { - return finished; - } - - public void parseMappings() - { - obf.parseMappings(mappings); - } -} diff --git a/src/main/java/codechicken/core/CCUpdateChecker.java b/src/main/java/codechicken/core/CCUpdateChecker.java new file mode 100644 index 0000000..ca69f93 --- /dev/null +++ b/src/main/java/codechicken/core/CCUpdateChecker.java @@ -0,0 +1,22 @@ +package codechicken.core; + +import com.google.common.base.Function; + +import cpw.mods.fml.relauncher.FMLInjectionData; + +public class CCUpdateChecker { + + public static void tick() {} + + public static void addUpdateMessage(String s) {} + + public static String mcVersion() { + return (String) FMLInjectionData.data()[4]; + } + + public static void updateCheck(final String mod, final String version) {} + + public static void updateCheck(String mod) {} + + public static void updateCheck(String url, Function handler) {} +} diff --git a/src/codechicken/core/ClassDiscoverer.java b/src/main/java/codechicken/core/ClassDiscoverer.java similarity index 67% rename from src/codechicken/core/ClassDiscoverer.java rename to src/main/java/codechicken/core/ClassDiscoverer.java index d36b248..a2abe9b 100644 --- a/src/codechicken/core/ClassDiscoverer.java +++ b/src/main/java/codechicken/core/ClassDiscoverer.java @@ -1,29 +1,31 @@ package codechicken.core; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; +import java.util.zip.ZipFile; import net.minecraft.launchwrapper.Launch; -import org.objectweb.asm.tree.ClassNode; -import codechicken.core.launch.CodeChickenCorePlugin; -import codechicken.lib.asm.ASMHelper; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; import com.google.common.collect.ImmutableList; +import codechicken.core.launch.CodeChickenCorePlugin; +import codechicken.lib.asm.ASMHelper; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModClassLoader; import cpw.mods.fml.relauncher.CoreModManager; -public class ClassDiscoverer -{ +@SuppressWarnings("unused") +public class ClassDiscoverer { + public IStringMatcher matcher; public String[] superclasses; public ArrayList> classes; @@ -35,15 +37,12 @@ public ClassDiscoverer(IStringMatcher matcher, Class... superclasses) { for (int i = 0; i < superclasses.length; i++) this.superclasses[i] = superclasses[i].getName().replace('.', '/'); - classes = new ArrayList>(); + classes = new ArrayList<>(); modClassLoader = (ModClassLoader) Loader.instance().getModClassLoader(); } public ClassDiscoverer(Class... superclasses) { - this(new IStringMatcher() - { - public boolean matches(String test) {return true;} - }, superclasses); + this(test -> true, superclasses); } public ArrayList> findClasses() { @@ -59,13 +58,11 @@ private void checkAddClass(String resource) { try { String classname = resource.replace(".class", "").replace("\\", ".").replace("/", "."); byte[] bytes = Launch.classLoader.getClassBytes(classname); - if (bytes == null) - return; + if (bytes == null) return; - ClassNode cnode = ASMHelper.createClassNode(bytes); + ClassNode cnode = ASMHelper.createClassNode(bytes, ClassReader.SKIP_CODE); for (String superclass : superclasses) - if (!cnode.interfaces.contains(superclass) && !cnode.superName.equals(superclass)) - return; + if (!cnode.interfaces.contains(superclass) && !cnode.superName.equals(superclass)) return; addClass(classname); } catch (IOException e) { @@ -83,49 +80,49 @@ private void addClass(String classname) { } private void findClasspathMods() { - List knownLibraries = ImmutableList.builder() - .addAll(modClassLoader.getDefaultLibraries()) + List knownLibraries = ImmutableList.builder().addAll(modClassLoader.getDefaultLibraries()) .addAll(CoreModManager.getLoadedCoremods()).build(); File[] minecraftSources = modClassLoader.getParentSources(); - HashSet searchedSources = new HashSet(); + HashSet searchedSources = new HashSet<>(); for (File minecraftSource : minecraftSources) { - if (searchedSources.contains(minecraftSource.getAbsolutePath())) - continue; + if (searchedSources.contains(minecraftSource.getAbsolutePath())) continue; searchedSources.add(minecraftSource.getAbsolutePath()); if (minecraftSource.isFile()) { if (!knownLibraries.contains(minecraftSource.getName())) { - FMLLog.fine("Found a minecraft related file at %s, examining for codechicken classes", minecraftSource.getAbsolutePath()); + FMLLog.fine( + "Found a minecraft related file at %s, examining for codechicken classes", + minecraftSource.getAbsolutePath()); try { readFromZipFile(minecraftSource); } catch (Exception e) { - CodeChickenCorePlugin.logger.error("Failed to scan " + minecraftSource.getAbsolutePath() + ", the zip file is invalid", e); + CodeChickenCorePlugin.logger.error( + "Failed to scan " + minecraftSource.getAbsolutePath() + ", the zip file is invalid", + e); } } } else if (minecraftSource.isDirectory()) { - FMLLog.fine("Found a minecraft related directory at %s, examining for codechicken classes", minecraftSource.getAbsolutePath()); + FMLLog.fine( + "Found a minecraft related directory at %s, examining for codechicken classes", + minecraftSource.getAbsolutePath()); readFromDirectory(minecraftSource, minecraftSource); } } } private void readFromZipFile(File file) throws IOException { - FileInputStream fileinputstream = new FileInputStream(file); - ZipInputStream zipinputstream = new ZipInputStream(fileinputstream); - do { - ZipEntry zipentry = zipinputstream.getNextEntry(); - if (zipentry == null) { - break; - } - String fullname = zipentry.getName().replace('\\', '/'); - int pos = fullname.lastIndexOf('/'); - String name = pos == -1 ? fullname : fullname.substring(pos + 1); - if (!zipentry.isDirectory() && matcher.matches(name)) { - checkAddClass(fullname); + try (ZipFile zipFile = new ZipFile(file)) { + Enumeration zipEntries = zipFile.entries(); + while (zipEntries.hasMoreElements()) { + ZipEntry zipentry = zipEntries.nextElement(); + String fullname = zipentry.getName().replace('\\', '/'); + int pos = fullname.lastIndexOf('/'); + String name = pos == -1 ? fullname : fullname.substring(pos + 1); + if (!zipentry.isDirectory() && matcher.matches(name)) { + checkAddClass(fullname); + } } } - while (true); - fileinputstream.close(); } private void readFromDirectory(File directory, File basedirectory) { diff --git a/src/codechicken/core/ClientUtils.java b/src/main/java/codechicken/core/ClientUtils.java similarity index 87% rename from src/codechicken/core/ClientUtils.java rename to src/main/java/codechicken/core/ClientUtils.java index 621f206..a02e5a5 100644 --- a/src/codechicken/core/ClientUtils.java +++ b/src/main/java/codechicken/core/ClientUtils.java @@ -1,12 +1,5 @@ package codechicken.core; -import codechicken.core.internal.CCCEventHandler; - -import cpw.mods.fml.common.FMLCommonHandler; -import cpw.mods.fml.common.ModContainer; -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; - import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraft.network.NetworkManager; @@ -14,8 +7,14 @@ import net.minecraft.util.EnumChatFormatting; import net.minecraft.world.World; -public class ClientUtils extends CommonUtils -{ +import codechicken.core.internal.CCCEventHandler; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class ClientUtils extends CommonUtils { + private static Minecraft mc() { return Minecraft.getMinecraft(); } @@ -24,15 +23,14 @@ public static World getWorld() { return mc().theWorld; } - public static boolean inWorld()//TODO unused + public static boolean inWorld() // TODO unused { return mc().getNetHandler() != null; } public static void openSMPGui(int windowId, GuiScreen gui) { mc().displayGuiScreen(gui); - if (windowId != 0) - mc().thePlayer.openContainer.windowId = windowId; + if (windowId != 0) mc().thePlayer.openContainer.windowId = windowId; } public static float getRenderFrame() { @@ -63,7 +61,8 @@ public static String getWorldSaveName() { public static void enhanceSupportersList(Object mod) { ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod); - mc.getMetadata().description = mc.getMetadata().description.replace("Supporters:", EnumChatFormatting.AQUA+"Supporters:"); + mc.getMetadata().description = mc.getMetadata().description + .replace("Supporters:", EnumChatFormatting.AQUA + "Supporters:"); GuiModListScroll.register(mod); } } diff --git a/src/codechicken/core/CommonUtils.java b/src/main/java/codechicken/core/CommonUtils.java similarity index 93% rename from src/codechicken/core/CommonUtils.java rename to src/main/java/codechicken/core/CommonUtils.java index 1368501..b482d19 100644 --- a/src/codechicken/core/CommonUtils.java +++ b/src/main/java/codechicken/core/CommonUtils.java @@ -2,15 +2,16 @@ import java.io.File; -import cpw.mods.fml.common.FMLCommonHandler; -import cpw.mods.fml.relauncher.FMLInjectionData; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityList; import net.minecraft.world.World; import net.minecraftforge.common.DimensionManager; -public class CommonUtils -{ +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.relauncher.FMLInjectionData; + +public class CommonUtils { + public static boolean isClient() { return FMLCommonHandler.instance().getSide().isClient(); } @@ -37,14 +38,12 @@ public static File getMinecraftDir() { } public static String getRelativePath(File parent, File child) { - if (parent.isFile() || !child.getPath().startsWith(parent.getPath())) - return null; + if (parent.isFile() || !child.getPath().startsWith(parent.getPath())) return null; return child.getPath().substring(parent.getPath().length() + 1); } - public static void registerHandledEntity(Class entityClass, String identifier) - { + public static void registerHandledEntity(Class entityClass, String identifier) { EntityList.classToStringMapping.put(entityClass, identifier); EntityList.stringToClassMapping.put(identifier, entityClass); } diff --git a/src/codechicken/core/GuiModListScroll.java b/src/main/java/codechicken/core/GuiModListScroll.java similarity index 64% rename from src/codechicken/core/GuiModListScroll.java rename to src/main/java/codechicken/core/GuiModListScroll.java index 8375fcf..3c01b4e 100644 --- a/src/codechicken/core/GuiModListScroll.java +++ b/src/main/java/codechicken/core/GuiModListScroll.java @@ -1,24 +1,24 @@ package codechicken.core; -import codechicken.core.launch.CodeChickenCorePlugin; -import codechicken.lib.gui.GuiDraw; -import codechicken.lib.vec.Rectangle4i; -import cpw.mods.fml.client.GuiModList; -import cpw.mods.fml.common.FMLCommonHandler; -import cpw.mods.fml.common.ModContainer; -import cpw.mods.fml.common.ModMetadata; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.client.MinecraftForgeClient; -import org.lwjgl.BufferUtils; -import org.lwjgl.input.Keyboard; -import org.lwjgl.opengl.GL11; - -import javax.imageio.ImageIO; - -import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL11.GL_ALWAYS; +import static org.lwjgl.opengl.GL11.GL_EQUAL; +import static org.lwjgl.opengl.GL11.GL_KEEP; +import static org.lwjgl.opengl.GL11.GL_PACK_ALIGNMENT; +import static org.lwjgl.opengl.GL11.GL_REPLACE; +import static org.lwjgl.opengl.GL11.GL_STENCIL_INDEX; +import static org.lwjgl.opengl.GL11.GL_STENCIL_TEST; +import static org.lwjgl.opengl.GL11.GL_UNPACK_ALIGNMENT; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; +import static org.lwjgl.opengl.GL11.GL_ZERO; +import static org.lwjgl.opengl.GL11.glColorMask; +import static org.lwjgl.opengl.GL11.glDisable; +import static org.lwjgl.opengl.GL11.glEnable; +import static org.lwjgl.opengl.GL11.glPixelStorei; import static org.lwjgl.opengl.GL11.glReadPixels; +import static org.lwjgl.opengl.GL11.glStencilFunc; +import static org.lwjgl.opengl.GL11.glStencilOp; -import java.awt.*; +import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -26,8 +26,25 @@ import java.util.LinkedList; import java.util.List; -public class GuiModListScroll -{ +import javax.imageio.ImageIO; + +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.MinecraftForgeClient; + +import org.lwjgl.BufferUtils; +import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; + +import codechicken.core.launch.CodeChickenCorePlugin; +import codechicken.lib.gui.GuiDraw; +import codechicken.lib.vec.Rectangle4i; +import cpw.mods.fml.client.GuiModList; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.ModMetadata; + +public class GuiModListScroll { + private static List scrollMods = new LinkedList(); public static void register(Object mod) { @@ -37,8 +54,7 @@ public static void register(Object mod) { private static void register(ModContainer mod) { if (MinecraftForgeClient.getStencilBits() == 0) CodeChickenCorePlugin.logger.error("Unable to do mod description scrolling due to lack of stencil buffer"); - else - scrollMods.add(mod); + else scrollMods.add(mod); } private static void screenshotStencil(int x) { @@ -49,11 +65,10 @@ private static void screenshotStencil(int x) { glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glReadPixels(0, 0, d.width, d.height, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, buf); - for(int i = 0; i < d.width; i++) - for(int j = 0; j < d.height; j++) - img.setRGB(i, d.height-j-1, buf.get(j * d.width + i) == 0 ? 0 : 0xFFFFFF); + for (int i = 0; i < d.width; i++) for (int j = 0; j < d.height; j++) + img.setRGB(i, d.height - j - 1, buf.get(j * d.width + i) == 0 ? 0 : 0xFFFFFF); try { - ImageIO.write(img, "png", new File("stencil"+x+".png")); + ImageIO.write(img, "png", new File("stencil" + x + ".png")); } catch (IOException e) { e.printStackTrace(); } @@ -63,36 +78,35 @@ private static void screenshotStencil(int x) { private static double scroll; private static double lastFrameTime; private static double timeStart; + public static void draw(GuiModList gui, int mouseX, int mouseY) { ModContainer selectedMod = ReflectionManager.getField(GuiModList.class, ModContainer.class, gui, "selectedMod"); - if(selectedMod != lastMod) { + if (selectedMod != lastMod) { lastMod = selectedMod; scroll = 0; timeStart = ClientUtils.getRenderTime(); } - if(!scrollMods.contains(selectedMod) || selectedMod.getMetadata().autogenerated) - return; + if (!scrollMods.contains(selectedMod) || selectedMod.getMetadata().autogenerated) return; int y1 = calcDescY(gui, selectedMod); int y1draw = y1 + 10; - int y2 = gui.height-38; + int y2 = gui.height - 38; int x1 = ReflectionManager.getField(GuiModList.class, Integer.class, gui, "listWidth") + 20; int x2 = gui.width - 20; - if(x2 - x1 <= 20) - return; + if (x2 - x1 <= 20) return; glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 1, 1); glColorMask(false, false, false, false); glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); - GuiDraw.drawRect(0, 0, gui.width, gui.height, -1);//clear stencil buffer + GuiDraw.drawRect(0, 0, gui.width, gui.height, -1); // clear stencil buffer screenshotStencil(1); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); - GuiDraw.drawRect(x1, y1, gui.width - x1, gui.height - y1, -1);//add description area (even below button) + GuiDraw.drawRect(x1, y1, gui.width - x1, gui.height - y1, -1); // add description area (even below button) glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); - GuiDraw.drawRect(gui.width / 2 - 75, y2, 200, 20, -1);//subtract done button + GuiDraw.drawRect(gui.width / 2 - 75, y2, 200, 20, -1); // subtract done button screenshotStencil(2); @@ -100,13 +114,13 @@ public static void draw(GuiModList gui, int mouseX, int mouseY) { glStencilFunc(GL_EQUAL, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - gui.drawDefaultBackground();//fill stencil with background + gui.drawDefaultBackground(); // fill stencil with background glColorMask(false, false, false, false); glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); GuiDraw.drawRect(0, 0, gui.width, gui.height, -1); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); - GuiDraw.drawRect(x1, y1draw, x2-x1, y2-y1draw, -1); + GuiDraw.drawRect(x1, y1draw, x2 - x1, y2 - y1draw, -1); screenshotStencil(3); @@ -114,10 +128,11 @@ public static void draw(GuiModList gui, int mouseX, int mouseY) { glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); String description = selectedMod.getMetadata().description; - int height = GuiDraw.fontRenderer.listFormattedStringToWidth(description, x2-x1).size()*GuiDraw.fontRenderer.FONT_HEIGHT; + int height = GuiDraw.fontRenderer.listFormattedStringToWidth(description, x2 - x1).size() + * GuiDraw.fontRenderer.FONT_HEIGHT; - boolean needsScroll = height > y2-y1draw; - if((ClientUtils.getRenderTime() - timeStart) > 40 && needsScroll) { + boolean needsScroll = height > y2 - y1draw; + if ((ClientUtils.getRenderTime() - timeStart) > 40 && needsScroll) { double dt = ClientUtils.getRenderTime() - lastFrameTime; if (new Rectangle4i(x1, y1draw, x2 - x1, y2 - y1draw).contains(mouseX, mouseY)) { double d = Keyboard.isKeyDown(Keyboard.KEY_UP) ? -1 : Keyboard.isKeyDown(Keyboard.KEY_DOWN) ? 1 : 0; @@ -128,36 +143,35 @@ public static void draw(GuiModList gui, int mouseX, int mouseY) { } lastFrameTime = ClientUtils.getRenderTime(); - //draw description + // draw description double dy = scroll % (height + 20); GL11.glPushMatrix(); - GL11.glTranslated(0, -dy, 0); - GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2-x1, 0xDDDDDD); - if(needsScroll) { - GL11.glTranslated(0, height + 20, 0); - GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2 - x1, 0xDDDDDD); - } + GL11.glTranslated(0, -dy, 0); + GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2 - x1, 0xDDDDDD); + if (needsScroll) { + GL11.glTranslated(0, height + 20, 0); + GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2 - x1, 0xDDDDDD); + } GL11.glPopMatrix(); glDisable(GL_STENCIL_TEST); } /** - * Does not add the last 10 px space before the description normally starts - * Ignores empty child mods expecting a background draw overwrite + * Does not add the last 10 px space before the description normally starts Ignores empty child mods expecting a + * background draw overwrite */ private static int calcDescY(GuiModList gui, ModContainer mod) { ModMetadata meta = mod.getMetadata(); int y = 35; - if(!!meta.logoFile.isEmpty() && ReflectionManager.getField(GuiModList.class, ResourceLocation.class, gui, "cachedLogo") != null) + if (!!meta.logoFile.isEmpty() + && ReflectionManager.getField(GuiModList.class, ResourceLocation.class, gui, "cachedLogo") != null) y += 65; y += 12; // title y += 40; // necessary lines - if(!meta.credits.isEmpty()) - y += 10; - if(!meta.childMods.isEmpty()) - y += 10; + if (!meta.credits.isEmpty()) y += 10; + if (!meta.childMods.isEmpty()) y += 10; return y; } } diff --git a/src/codechicken/core/IGuiPacketSender.java b/src/main/java/codechicken/core/IGuiPacketSender.java similarity index 78% rename from src/codechicken/core/IGuiPacketSender.java rename to src/main/java/codechicken/core/IGuiPacketSender.java index 30b5783..60bc354 100644 --- a/src/codechicken/core/IGuiPacketSender.java +++ b/src/main/java/codechicken/core/IGuiPacketSender.java @@ -2,7 +2,7 @@ import net.minecraft.entity.player.EntityPlayerMP; -public interface IGuiPacketSender -{ +public interface IGuiPacketSender { + void sendPacket(EntityPlayerMP player, int windowId); } diff --git a/src/codechicken/core/IStringMatcher.java b/src/main/java/codechicken/core/IStringMatcher.java similarity index 66% rename from src/codechicken/core/IStringMatcher.java rename to src/main/java/codechicken/core/IStringMatcher.java index 12e320c..cff5405 100644 --- a/src/codechicken/core/IStringMatcher.java +++ b/src/main/java/codechicken/core/IStringMatcher.java @@ -1,6 +1,6 @@ package codechicken.core; -public interface IStringMatcher -{ +public interface IStringMatcher { + public boolean matches(String test); } diff --git a/src/codechicken/core/ProfileTimer.java b/src/main/java/codechicken/core/ProfileTimer.java similarity index 63% rename from src/codechicken/core/ProfileTimer.java rename to src/main/java/codechicken/core/ProfileTimer.java index 86b4d8a..b6a2a8a 100644 --- a/src/codechicken/core/ProfileTimer.java +++ b/src/main/java/codechicken/core/ProfileTimer.java @@ -1,7 +1,9 @@ package codechicken.core; -public class ProfileTimer -{ +import codechicken.core.launch.CodeChickenCorePlugin; + +public class ProfileTimer { + public double decay; public long startTime; public long nanoTime; @@ -28,12 +30,11 @@ public void start() { } public void end() { - long t = System.nanoTime()-startTime; - nanoTime = (long)(nanoTime*decay+t*(1-decay)); + long t = System.nanoTime() - startTime; + nanoTime = (long) (nanoTime * decay + t * (1 - decay)); scanCount++; - if(logScans > 0 && scanCount % logScans == 0) - System.out.println("Profiled " + logName + " " + nanoTime + "ns"); - + if (logScans > 0 && scanCount % logScans == 0) + CodeChickenCorePlugin.logger.info("Profiled " + logName + " " + nanoTime + "ns"); } } diff --git a/src/main/java/codechicken/core/ReflectionManager.java b/src/main/java/codechicken/core/ReflectionManager.java new file mode 100644 index 0000000..0948de6 --- /dev/null +++ b/src/main/java/codechicken/core/ReflectionManager.java @@ -0,0 +1,260 @@ +package codechicken.core; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; + +import codechicken.lib.asm.ObfMapping; + +public class ReflectionManager { + + public static HashMap, Class> primitiveWrappers = new HashMap, Class>(); + + static { + primitiveWrappers.put(Integer.TYPE, Integer.class); + primitiveWrappers.put(Short.TYPE, Short.class); + primitiveWrappers.put(Byte.TYPE, Byte.class); + primitiveWrappers.put(Long.TYPE, Long.class); + primitiveWrappers.put(Double.TYPE, Double.class); + primitiveWrappers.put(Float.TYPE, Float.class); + primitiveWrappers.put(Boolean.TYPE, Boolean.class); + primitiveWrappers.put(Character.TYPE, Character.class); + } + + public static boolean isInstance(Class class1, Object obj) { + Class primitive = primitiveWrappers.get(class1); + if (primitive != null) { + if (primitive == Long.class && Long.class.isInstance(obj)) return true; + if ((primitive == Long.class || primitive == Integer.class) && Integer.class.isInstance(obj)) return true; + if ((primitive == Long.class || primitive == Integer.class || primitive == Short.class) + && Short.class.isInstance(obj)) + return true; + if ((primitive == Long.class || primitive == Integer.class + || primitive == Short.class + || primitive == Byte.class) && Integer.class.isInstance(obj)) + return true; + + if (primitive == Double.class && Double.class.isInstance(obj)) return true; + if ((primitive == Double.class || primitive == Float.class) && Float.class.isInstance(obj)) return true; + + return primitive.isInstance(obj); + } + return class1.isInstance(obj); + } + + public static Class findClass(String name) { + return findClass(name, true); + } + + public static boolean classExists(String name) { + return findClass(name, false) != null; + } + + public static Class findClass(String name, boolean init) { + try { + return Class.forName(name, init, ReflectionManager.class.getClassLoader()); + } catch (ClassNotFoundException cnfe) { + try { + return Class.forName("net.minecraft.src." + name, init, ReflectionManager.class.getClassLoader()); + } catch (ClassNotFoundException cnfe2) { + return null; + } + } + } + + public static void setField(Class class1, Object instance, String name, Object value) + throws IllegalArgumentException, IllegalAccessException { + setField(class1, instance, new String[] { name }, value); + } + + public static void setField(Class class1, Object instance, String[] names, Object value) + throws IllegalArgumentException, IllegalAccessException { + for (Field field : class1.getDeclaredFields()) { + boolean match = false; + for (String name : names) { + if (field.getName().equals(name)) { + match = true; + break; + } + } + if (!match) { + continue; + } + + field.setAccessible(true); + field.set(instance, value); + return; + } + } + + public static void setField(Class class1, Object instance, int fieldindex, Object value) + throws IllegalArgumentException, IllegalAccessException { + Field field = class1.getDeclaredFields()[fieldindex]; + field.setAccessible(true); + field.set(instance, value); + } + + /** + * Static function void return type single name + */ + public static void callMethod(Class class1, String name, Object... params) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + callMethod(class1, null, new String[] { name }, params); + } + + /** + * Static function void return type single name + */ + public static void callMethod(Class class1, String[] names, Object... params) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + callMethod(class1, null, names, params); + } + + /** + * void return type single name + */ + public static void callMethod(Class class1, Object instance, String name, Object... params) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + callMethod(class1, null, instance, new String[] { name }, params); + } + + /** + * void return type + */ + public static void callMethod(Class class1, Object instance, String[] names, Object... params) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + callMethod(class1, null, instance, names, params); + } + + /** + * Static method single name + */ + public static R callMethod(Class class1, Class returntype, String name, Object... params) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + return callMethod(class1, returntype, null, new String[] { name }, params); + } + + /** + * Static method + */ + public static R callMethod(Class class1, Class returntype, String[] names, Object... params) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + return callMethod(class1, returntype, null, names, params); + } + + /** + * sinlge name + */ + public static R callMethod(Class class1, Class returntype, Object instance, String name, Object... params) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + return callMethod(class1, returntype, instance, new String[] { name }, params); + } + + public static R callMethod(Class class1, Class returntype, Object instance, String[] names, + Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + nextMethod: for (Method method : class1.getDeclaredMethods()) { + boolean match = false; + for (String name : names) { + if (method.getName().equals(name)) { + match = true; + break; + } + } + if (!match) { + continue; + } + + Class[] paramtypes = method.getParameterTypes(); + if (paramtypes.length != params.length) continue; + + for (int i = 0; i < params.length; i++) { + if (!isInstance(paramtypes[i], params[i])) continue nextMethod; + } + + method.setAccessible(true); + return (R) method.invoke(instance, params); + } + return null; + } + + public static T getField(Class class1, Class fieldType, Object instance, int fieldIndex) + throws IllegalArgumentException, IllegalAccessException { + Field field = class1.getDeclaredFields()[fieldIndex]; + field.setAccessible(true); + return (T) field.get(instance); + } + + public static T getField(Class class1, Class fieldType, Object instance, String fieldName) { + try { + Field field = class1.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(instance); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static T newInstance(Class class1, Object... params) + throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { + nextMethod: for (Constructor constructor : class1.getDeclaredConstructors()) { + Class[] paramtypes = constructor.getParameterTypes(); + if (paramtypes.length != params.length) continue; + + for (int i = 0; i < params.length; i++) { + if (!isInstance(paramtypes[i], params[i])) continue nextMethod; + } + + constructor.setAccessible(true); + return (T) constructor.newInstance(params); + } + return null; + } + + public static boolean hasField(Class class1, String fieldName) { + try { + class1.getDeclaredField(fieldName); + return true; + } catch (NoSuchFieldException nfe) { + return false; + } + } + + public static T get(Field field, Class class1) { + return get(field, class1, null); + } + + public static T get(Field field, Class class1, Object instance) { + try { + return (T) field.get(instance); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void set(Field field, Object value) { + set(field, null, value); + } + + public static void set(Field field, Object instance, Object value) { + try { + field.set(instance, value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Field getField(ObfMapping mapping) { + mapping.toRuntime(); + + try { + Class clazz = ReflectionManager.class.getClassLoader().loadClass(mapping.javaClass()); + Field field = clazz.getDeclaredField(mapping.s_name); + field.setAccessible(true); + return field; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/codechicken/core/ServerUtils.java b/src/main/java/codechicken/core/ServerUtils.java similarity index 69% rename from src/codechicken/core/ServerUtils.java rename to src/main/java/codechicken/core/ServerUtils.java index 7b9db6d..8daf323 100644 --- a/src/codechicken/core/ServerUtils.java +++ b/src/main/java/codechicken/core/ServerUtils.java @@ -7,16 +7,18 @@ import java.util.Locale; import java.util.Map; -import codechicken.lib.asm.ObfMapping; -import com.mojang.authlib.GameProfile; -import net.minecraft.server.MinecraftServer; -import net.minecraft.inventory.Container; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.Container; +import net.minecraft.server.MinecraftServer; import net.minecraft.util.IChatComponent; -public class ServerUtils extends CommonUtils -{ +import com.mojang.authlib.GameProfile; + +import codechicken.lib.asm.ObfMapping; + +public class ServerUtils extends CommonUtils { + public static MinecraftServer mc() { return MinecraftServer.getServer(); } @@ -31,9 +33,7 @@ public static List getPlayers() { public static ArrayList getPlayersInDimension(int dimension) { ArrayList players = new ArrayList(); - for (EntityPlayer p : getPlayers()) - if(p.dimension == dimension) - players.add(p); + for (EntityPlayer p : getPlayers()) if (p.dimension == dimension) players.add(p); return players; } @@ -50,13 +50,21 @@ public static void openSMPContainer(EntityPlayerMP player, Container container, private static Field field_152661_c; private static Class c_ProfileEntry; private static Method func_152668_a; + static { try { - field_152661_c = ReflectionManager.getField(new ObfMapping("net/minecraft/server/management/PlayerProfileCache", "field_152661_c", "[Ljava/util/Map;")); - c_ProfileEntry = ServerUtils.class.getClassLoader().loadClass("net.minecraft.server.management.PlayerProfileCache$ProfileEntry"); + field_152661_c = ReflectionManager.getField( + new ObfMapping( + "net/minecraft/server/management/PlayerProfileCache", + "field_152661_c", + "[Ljava/util/Map;")); + c_ProfileEntry = ServerUtils.class.getClassLoader() + .loadClass("net.minecraft.server.management.PlayerProfileCache$ProfileEntry"); func_152668_a = c_ProfileEntry.getDeclaredMethod( - new ObfMapping("net/minecraft/server/management/PlayerProfileCache$ProfileEntry", - "func_152668_a", "()Lcom/mojang/authlib/GameProfile;").toRuntime().s_name); + new ObfMapping( + "net/minecraft/server/management/PlayerProfileCache$ProfileEntry", + "func_152668_a", + "()Lcom/mojang/authlib/GameProfile;").toRuntime().s_name); func_152668_a.setAccessible(true); } catch (Exception e) { @@ -66,14 +74,12 @@ public static void openSMPContainer(EntityPlayerMP player, Container container, public static GameProfile getGameProfile(String username) { EntityPlayer player = getPlayer(username); - if(player != null) - return player.getGameProfile(); + if (player != null) return player.getGameProfile(); username = username.toLowerCase(Locale.ROOT); - try {//use reflection to bypass saving the game profiles every time we ask the cache for one + try { // use reflection to bypass saving the game profiles every time we ask the cache for one Object cacheEntry = ((Map) field_152661_c.get(mc().func_152358_ax())).get(username); - if(cacheEntry != null) - return (GameProfile) func_152668_a.invoke(cacheEntry); + if (cacheEntry != null) return (GameProfile) func_152668_a.invoke(cacheEntry); } catch (Exception e) { throw new RuntimeException(e); } @@ -90,7 +96,6 @@ public static boolean isPlayerOwner(String username) { } public static void sendChatToAll(IChatComponent msg) { - for(EntityPlayer p : getPlayers()) - p.addChatComponentMessage(msg); + for (EntityPlayer p : getPlayers()) p.addChatComponentMessage(msg); } } diff --git a/src/codechicken/core/TaskProfiler.java b/src/main/java/codechicken/core/TaskProfiler.java similarity index 58% rename from src/codechicken/core/TaskProfiler.java rename to src/main/java/codechicken/core/TaskProfiler.java index c1ca80d..92a9769 100644 --- a/src/codechicken/core/TaskProfiler.java +++ b/src/main/java/codechicken/core/TaskProfiler.java @@ -5,61 +5,53 @@ import java.util.List; import java.util.Map.Entry; -public class TaskProfiler -{ - public static class ProfilerResult - { +public class TaskProfiler { + + public static class ProfilerResult { + public final String name; public final long time; public final double fraction; - - public ProfilerResult(String name, long time, long totalTime) - { + + public ProfilerResult(String name, long time, long totalTime) { this.name = name; this.time = time; - fraction = time/(double)totalTime; + fraction = time / (double) totalTime; } } - + public HashMap times = new HashMap(); - + public String currentSection; private long startTime; private long totalTime; - - public void start(String section) - { - if(currentSection != null) - end(); - + + public void start(String section) { + if (currentSection != null) end(); + currentSection = section; startTime = System.nanoTime(); } - - public void end() - { - long time = System.nanoTime()-startTime; - totalTime+=time; - + + public void end() { + long time = System.nanoTime() - startTime; + totalTime += time; + Long prev = times.get(currentSection); - if(prev == null) - prev = 0L; - times.put(currentSection, prev+time); + if (prev == null) prev = 0L; + times.put(currentSection, prev + time); currentSection = null; } - - public List getResults() - { + + public List getResults() { ArrayList results = new ArrayList(times.size()); - for(Entry e : times.entrySet()) + for (Entry e : times.entrySet()) results.add(new ProfilerResult(e.getKey(), e.getValue(), totalTime)); return results; } - public void clear() - { - if(currentSection != null) - end(); + public void clear() { + if (currentSection != null) end(); times.clear(); totalTime = 0; } diff --git a/src/codechicken/core/asm/CodeChickenAccessTransformer.java b/src/main/java/codechicken/core/asm/CodeChickenAccessTransformer.java similarity index 51% rename from src/codechicken/core/asm/CodeChickenAccessTransformer.java rename to src/main/java/codechicken/core/asm/CodeChickenAccessTransformer.java index d948541..552fef9 100644 --- a/src/codechicken/core/asm/CodeChickenAccessTransformer.java +++ b/src/main/java/codechicken/core/asm/CodeChickenAccessTransformer.java @@ -5,35 +5,19 @@ import com.google.common.collect.ImmutableBiMap; -import codechicken.lib.asm.ObfMapping; - import cpw.mods.fml.common.asm.transformers.AccessTransformer; import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; -public class CodeChickenAccessTransformer extends AccessTransformer -{ - private static boolean makeAllPublic; - private static Field f_classNameBiMap; - private static Object emptyMap = ImmutableBiMap.of(); - - public CodeChickenAccessTransformer() throws IOException { - super(); - loadPublicConfig(); - } - - private void loadPublicConfig() { - if (ObfMapping.obfuscated) - return; - - makeAllPublic = CodeChickenCoreModContainer.config.getTag("dev.runtimePublic") - .setComment("Enabling this setting will make all minecraft classes public at runtime in MCP just as they are in modloader." + - "\nYou should ONLY use this when you are testing with a mod that relies on runtime publicity and doesn't include access transformers." + - "\nSuch mods are doing the wrong thing and should be fixed.") - .getBooleanValue(false); +// This AccessTransformer will make all minecraft classes public at runtime in MCP just as they are in modloader. +// You should ONLY use this when you are testing with a mod that relies on runtime publicity and doesn't include +// access transformers. Such mods are doing the wrong thing and should be fixed. +public class CodeChickenAccessTransformer extends AccessTransformer { - if (!makeAllPublic) - return; + private static final Field f_classNameBiMap; + @SuppressWarnings("FieldMayBeFinal") + private static Object emptyMap = ImmutableBiMap.of(); + static { try { f_classNameBiMap = FMLDeobfuscatingRemapper.class.getDeclaredField("classNameBiMap"); f_classNameBiMap.setAccessible(true); @@ -42,18 +26,18 @@ private void loadPublicConfig() { } } + public CodeChickenAccessTransformer() throws IOException {} + @Override public byte[] transform(String name, String transformedName, byte[] bytes) { - boolean setPublic = makeAllPublic && name.startsWith("net.minecraft."); - if (setPublic) - setClassMap(name); + boolean setPublic = name.startsWith("net.minecraft."); + if (setPublic) setClassMap(name); bytes = super.transform(name, transformedName, bytes); - if (setPublic) - restoreClassMap(); + if (setPublic) restoreClassMap(); return bytes; } - private void restoreClassMap() { + private static void restoreClassMap() { try { f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, emptyMap); } catch (Exception e) { @@ -61,7 +45,7 @@ private void restoreClassMap() { } } - private void setClassMap(String name) { + private static void setClassMap(String name) { try { f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, ImmutableBiMap.of(name.replace('.', '/'), "")); } catch (Exception e) { diff --git a/src/codechicken/core/asm/CodeChickenCoreModContainer.java b/src/main/java/codechicken/core/asm/CodeChickenCoreModContainer.java similarity index 73% rename from src/codechicken/core/asm/CodeChickenCoreModContainer.java rename to src/main/java/codechicken/core/asm/CodeChickenCoreModContainer.java index ef5f457..450ce5b 100644 --- a/src/codechicken/core/asm/CodeChickenCoreModContainer.java +++ b/src/main/java/codechicken/core/asm/CodeChickenCoreModContainer.java @@ -1,44 +1,45 @@ package codechicken.core.asm; -import java.io.File; import java.util.LinkedList; import java.util.List; -import codechicken.core.CCUpdateChecker; -import codechicken.core.ClientUtils; -import codechicken.core.featurehack.LiquidTextures; -import codechicken.core.internal.CCCEventHandler; -import codechicken.core.launch.CodeChickenCorePlugin; -import codechicken.lib.config.ConfigFile; +import net.minecraftforge.common.MinecraftForge; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; -import cpw.mods.fml.common.*; +import codechicken.core.ClientUtils; +import codechicken.core.featurehack.LiquidTextures; +import codechicken.core.internal.CCCEventHandler; +import codechicken.core.launch.CodeChickenCorePlugin; +import cpw.mods.fml.common.DummyModContainer; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.LoadController; +import cpw.mods.fml.common.ModMetadata; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.versioning.ArtifactVersion; import cpw.mods.fml.common.versioning.VersionParser; import cpw.mods.fml.common.versioning.VersionRange; -import net.minecraftforge.common.MinecraftForge; -public class CodeChickenCoreModContainer extends DummyModContainer -{ - public static ConfigFile config; +public class CodeChickenCoreModContainer extends DummyModContainer { - public static void loadConfig() { - if(config == null) - config = new ConfigFile(new File(CodeChickenCorePlugin.minecraftDir, "config/CodeChickenCore.cfg")).setComment("CodeChickenCore configuration file."); + public CodeChickenCoreModContainer() { + super(getModMetadata()); } - public CodeChickenCoreModContainer() { - super(MetadataCollection.from(MetadataCollection.class.getResourceAsStream("/cccmod.info"), "CodeChickenCore").getMetadataForId("CodeChickenCore", null)); + private static ModMetadata getModMetadata() { + final ModMetadata modMetadata = new ModMetadata(); + modMetadata.name = "CodeChicken Core"; + modMetadata.modId = "CodeChickenCore"; + modMetadata.version = Tags.VERSION; + return modMetadata; } @Override public List getDependants() { LinkedList deps = new LinkedList(); - if(!getVersion().contains("$")) { + if (!getVersion().contains("$")) { deps.add(VersionParser.parseVersionReference("Forge@[10.13.3,)")); deps.add(VersionParser.parseVersionReference("NotEnoughItems@[1.0.5,)")); deps.add(VersionParser.parseVersionReference("EnderStorage@[1.4.7,)")); @@ -57,16 +58,12 @@ public boolean registerBus(EventBus bus, LoadController controller) { @Subscribe public void preInit(FMLPreInitializationEvent event) { - if (event.getSide().isClient()) - LiquidTextures.init(); + if (event.getSide().isClient()) LiquidTextures.init(); } @Subscribe public void init(FMLInitializationEvent event) { if (event.getSide().isClient()) { - if (config.getTag("checkUpdates").getBooleanValue(true)) - CCUpdateChecker.updateCheck(getModId()); - ClientUtils.enhanceSupportersList("CodeChickenCore"); FMLCommonHandler.instance().bus().register(new CCCEventHandler()); diff --git a/src/main/java/codechicken/core/asm/DefaultImplementationTransformer.java b/src/main/java/codechicken/core/asm/DefaultImplementationTransformer.java new file mode 100644 index 0000000..641404a --- /dev/null +++ b/src/main/java/codechicken/core/asm/DefaultImplementationTransformer.java @@ -0,0 +1,96 @@ +package codechicken.core.asm; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.Launch; + +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import codechicken.lib.asm.ASMHelper; +import codechicken.lib.asm.ObfMapping; +import cpw.mods.fml.relauncher.FMLRelaunchLog; + +public class DefaultImplementationTransformer implements IClassTransformer { + + private static final HashMap impls = new HashMap<>(); + + public static void registerDefaultImpl(String iname, String cname) { + if (impls.isEmpty()) { + String name = DefaultImplementationTransformer.class.getName(); + FMLRelaunchLog.finer("Registering transformer %s", name); + Launch.classLoader.registerTransformer(name); + } + impls.put(iname.replace('.', '/'), new InterfaceImpl(iname, cname)); + } + + @Override + public byte[] transform(String name, String transformedName, byte[] bytes) { + if (transformedName.startsWith("net.minecraft") || impls.isEmpty()) return bytes; + + ClassNode cnode = ASMHelper.createClassNode(bytes); + boolean changed = false; + for (String iname : cnode.interfaces) { + InterfaceImpl impl = impls.get(iname); + if (impl != null) changed |= impl.patch(cnode); + } + + return changed ? ASMHelper.createBytes(cnode, 0) : bytes; + } + + static class InterfaceImpl { + + public final String iname; + public ArrayList impls = new ArrayList<>(); + + public InterfaceImpl(String iname, String cname) { + this.iname = iname; + HashSet names = new HashSet<>(); + ClassNode inode = getClassNode(iname); + for (MethodNode method : inode.methods) names.add(method.name + method.desc); + + ClassNode cnode = getClassNode(cname); + for (MethodNode method : cnode.methods) if (names.contains(method.name + method.desc)) { + impls.add(method); + method.desc = new ObfMapping(cnode.name, method.name, method.desc).toRuntime().s_desc; + } + } + + public boolean patch(ClassNode cnode) { + LinkedList names = new LinkedList<>(); + for (MethodNode method : cnode.methods) { + ObfMapping m = new ObfMapping(cnode.name, method.name, method.desc).toRuntime(); + names.add(m.s_name + m.s_desc); + } + + boolean changed = false; + for (MethodNode impl : impls) { + if (names.contains(impl.name + impl.desc)) continue; + + MethodNode copy = new MethodNode( + impl.access, + impl.name, + impl.desc, + impl.signature, + impl.exceptions == null ? null : impl.exceptions.toArray(new String[0])); + ASMHelper.copy(impl, copy); + cnode.methods.add(impl); + changed = true; + } + return changed; + } + + private static ClassNode getClassNode(String name) { + try { + return ASMHelper.createClassNode(Launch.classLoader.getClassBytes(name.replace('/', '.'))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/src/main/java/codechicken/core/asm/DelegatedTransformer.java b/src/main/java/codechicken/core/asm/DelegatedTransformer.java new file mode 100644 index 0000000..0b7f60f --- /dev/null +++ b/src/main/java/codechicken/core/asm/DelegatedTransformer.java @@ -0,0 +1,198 @@ +package codechicken.core.asm; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Map; +import java.util.Stack; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; + +import codechicken.core.launch.CodeChickenCorePlugin; +import cpw.mods.fml.relauncher.FMLRelaunchLog; + +public class DelegatedTransformer implements IClassTransformer { + + private static final ArrayList delegatedTransformers; + private static final Method m_defineClass; + private static final Field f_cachedClasses; + private static IClassTransformer[] transformers; + + static { + delegatedTransformers = new ArrayList<>(); + try { + m_defineClass = ClassLoader.class + .getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE); + m_defineClass.setAccessible(true); + f_cachedClasses = LaunchClassLoader.class.getDeclaredField("cachedClasses"); + f_cachedClasses.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (basicClass == null) return null; + for (IClassTransformer t : transformers) { + basicClass = t.transform(name, transformedName, basicClass); + } + return basicClass; + } + + public static void load() { + scanModsForDelegatedTransformers(); + final int size = delegatedTransformers.size(); + if (size == 0) { + CodeChickenCorePlugin.logger + .debug("No delegated transformer found, skipping registration of main DelegatedTransformer."); + return; + } + CodeChickenCorePlugin.logger + .debug("Found " + size + " delegated transformers, registering main DelegatedTransformer."); + transformers = delegatedTransformers.toArray(new IClassTransformer[0]); + String name = DelegatedTransformer.class.getName(); + FMLRelaunchLog.finer("Registering transformer %s", name); + Launch.classLoader.registerTransformer(name); + } + + private static void scanModsForDelegatedTransformers() { + File modsDir = new File(CodeChickenCorePlugin.minecraftDir, "mods"); + if (modsDir.exists()) { + final File[] files = modsDir.listFiles(); + if (files != null) { + for (File file : files) { + scanMod(file); + } + } + } + File versionModsDir = new File( + CodeChickenCorePlugin.minecraftDir, + "mods/" + CodeChickenCorePlugin.currentMcVersion); + if (versionModsDir.exists()) { + final File[] files = versionModsDir.listFiles(); + if (files != null) { + for (File file : files) { + scanMod(file); + } + } + } + } + + private static void scanMod(File file) { + if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) return; + + try { + try (JarFile jar = new JarFile(file)) { + Manifest manifest = jar.getManifest(); + if (manifest == null) return; + Attributes attr = manifest.getMainAttributes(); + if (attr == null) return; + + String transformer = attr.getValue("CCTransformer"); + if (transformer != null) { + addTransformer(transformer, jar, file); + } + } + } catch (Exception e) { + CodeChickenCorePlugin.logger.error("CodeChickenCore: Failed to read jar file: " + file.getName(), e); + } + } + + public static void addTransformer(String transformer, JarFile jar, File jarFile) { + CodeChickenCorePlugin.logger.debug("Adding CCTransformer: " + transformer); + try { + byte[] bytes; + bytes = Launch.classLoader.getClassBytes(transformer); + + if (bytes == null) { + String resourceName = transformer.replace('.', '/') + ".class"; + ZipEntry entry = jar.getEntry(resourceName); + if (entry == null) throw new Exception( + "Failed to add transformer: " + transformer + + ". Entry not found in jar file " + + jarFile.getName()); + + bytes = readFully(jar.getInputStream(entry)); + } + + defineDependancies(bytes, jar, jarFile); + Class clazz = defineClass(transformer, bytes); + + if (!IClassTransformer.class.isAssignableFrom(clazz)) throw new Exception( + "Failed to add transformer: " + transformer + " is not an instance of IClassTransformer"); + + IClassTransformer classTransformer; + try { + classTransformer = (IClassTransformer) clazz.getDeclaredConstructor(File.class).newInstance(jarFile); + } catch (NoSuchMethodException nsme) { + classTransformer = (IClassTransformer) clazz.newInstance(); + } + delegatedTransformers.add(classTransformer); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile) throws Exception { + defineDependancies(bytes, jar, jarFile, new Stack()); + } + + private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile, Stack depStack) + throws Exception { + ClassReader reader = new ClassReader(bytes); + DependancyLister lister = new DependancyLister(Opcodes.ASM4); + reader.accept(lister, 0); + + depStack.push(reader.getClassName()); + + for (String dependancy : lister.getDependancies()) { + if (depStack.contains(dependancy)) continue; + + try { + Launch.classLoader.loadClass(dependancy.replace('/', '.')); + } catch (ClassNotFoundException cnfe) { + ZipEntry entry = jar.getEntry(dependancy + ".class"); + if (entry == null) + throw new Exception("Dependency " + dependancy + " not found in jar file " + jarFile.getName()); + + byte[] depbytes = readFully(jar.getInputStream(entry)); + defineDependancies(depbytes, jar, jarFile, depStack); + + CodeChickenCorePlugin.logger.debug("Defining dependancy: " + dependancy); + + defineClass(dependancy.replace('/', '.'), depbytes); + } + } + + depStack.pop(); + } + + private static Class defineClass(String classname, byte[] bytes) throws Exception { + Class clazz = (Class) m_defineClass.invoke(Launch.classLoader, classname, bytes, 0, bytes.length); + ((Map>) f_cachedClasses.get(Launch.classLoader)).put(classname, clazz); + return clazz; + } + + public static byte[] readFully(InputStream stream) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(stream.available()); + int r; + while ((r = stream.read()) != -1) { + bos.write(r); + } + return bos.toByteArray(); + } +} diff --git a/src/codechicken/core/asm/DependancyLister.java b/src/main/java/codechicken/core/asm/DependancyLister.java similarity index 62% rename from src/codechicken/core/asm/DependancyLister.java rename to src/main/java/codechicken/core/asm/DependancyLister.java index a732bbf..ddbc52c 100644 --- a/src/codechicken/core/asm/DependancyLister.java +++ b/src/main/java/codechicken/core/asm/DependancyLister.java @@ -12,84 +12,70 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -public class DependancyLister extends ClassVisitor -{ +public class DependancyLister extends ClassVisitor { + private static Pattern classdesc = Pattern.compile("L(.+?);"); - - private class DependancyMethodLister extends MethodVisitor - { - public DependancyMethodLister(int api) - { + + private class DependancyMethodLister extends MethodVisitor { + + public DependancyMethodLister(int api) { super(api); } - + @Override - public void visitFieldInsn(int opcode, String owner, String name, String desc) - { + public void visitFieldInsn(int opcode, String owner, String name, String desc) { dependDesc(desc); } - + @Override - public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) - { + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { dependDesc(desc); } - + @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc) - { + public void visitMethodInsn(int opcode, String owner, String name, String desc) { depend(owner); dependDesc(desc); } } - + private HashSet dependancies = new HashSet(); - public DependancyLister(int api) - { + public DependancyLister(int api) { super(api); } - + @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) - { + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { dependDesc(desc); return null; } - - private void dependDesc(String desc) - { + + private void dependDesc(String desc) { Matcher match = classdesc.matcher(desc); - while(match.find()) - { + while (match.find()) { String s = match.group(); - depend(s.substring(1, s.length()-1)); + depend(s.substring(1, s.length() - 1)); } } - private void depend(String classname) - { + private void depend(String classname) { dependancies.add(classname); } @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) - { + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { dependDesc(desc); return new DependancyMethodLister(Opcodes.ASM4); } - + @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) - { + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { depend(superName); - if(interfaces != null) - for(String interfacename : interfaces) - depend(interfacename); + if (interfaces != null) for (String interfacename : interfaces) depend(interfacename); } - - public List getDependancies() - { + + public List getDependancies() { return new ArrayList(dependancies); } } diff --git a/src/codechicken/core/asm/InterfaceDependancies.java b/src/main/java/codechicken/core/asm/InterfaceDependancies.java similarity index 75% rename from src/codechicken/core/asm/InterfaceDependancies.java rename to src/main/java/codechicken/core/asm/InterfaceDependancies.java index 9048a0f..4d247a1 100644 --- a/src/codechicken/core/asm/InterfaceDependancies.java +++ b/src/main/java/codechicken/core/asm/InterfaceDependancies.java @@ -5,10 +5,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; - @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -public @interface InterfaceDependancies -{ - -} +@Target({ ElementType.TYPE }) +public @interface InterfaceDependancies {} diff --git a/src/codechicken/core/asm/MCPDeobfuscationTransformer.java b/src/main/java/codechicken/core/asm/MCPDeobfuscationTransformer.java similarity index 74% rename from src/codechicken/core/asm/MCPDeobfuscationTransformer.java rename to src/main/java/codechicken/core/asm/MCPDeobfuscationTransformer.java index 45d6d53..5bb6abb 100644 --- a/src/codechicken/core/asm/MCPDeobfuscationTransformer.java +++ b/src/main/java/codechicken/core/asm/MCPDeobfuscationTransformer.java @@ -2,12 +2,17 @@ import java.io.IOException; import java.lang.reflect.Field; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; -import codechicken.lib.asm.ASMInit; -import cpw.mods.fml.relauncher.IFMLLoadingPlugin; +import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; + import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; @@ -22,24 +27,18 @@ import codechicken.lib.asm.CC_ClassWriter; import codechicken.lib.asm.ObfMapping; import codechicken.obfuscator.IHeirachyEvaluator; -import codechicken.obfuscator.ObfuscationRun; import codechicken.obfuscator.ObfuscationMap.ObfuscationEntry; +import codechicken.obfuscator.ObfuscationRun; import cpw.mods.fml.common.asm.transformers.AccessTransformer; +import cpw.mods.fml.relauncher.IFMLLoadingPlugin; -import net.minecraft.launchwrapper.IClassTransformer; -import net.minecraft.launchwrapper.LaunchClassLoader; +public class MCPDeobfuscationTransformer implements IClassTransformer, Opcodes, IHeirachyEvaluator { -public class MCPDeobfuscationTransformer implements IClassTransformer, Opcodes, IHeirachyEvaluator -{ - static { - ASMInit.init(); - } + public static class LoadPlugin implements IFMLLoadingPlugin { - public static class LoadPlugin implements IFMLLoadingPlugin - { @Override public String[] getASMTransformerClass() { - return new String[0]; + return null; } @Override @@ -78,7 +77,8 @@ public String getAccessTransformerClass() { try { f_transformers = LaunchClassLoader.class.getDeclaredField("transformers"); f_modifiers = AccessTransformer.class.getDeclaredField("modifiers"); - Class c_Modifier = Class.forName(AccessTransformer.class.getName() + "$Modifier", false, Launch.classLoader); + Class c_Modifier = Class + .forName(AccessTransformer.class.getName() + "$Modifier", false, Launch.classLoader); f_Modifier_name = c_Modifier.getDeclaredField("name"); f_Modifier_desc = c_Modifier.getDeclaredField("desc"); @@ -113,24 +113,20 @@ private static List getTransformers() { } public static void load() { - CodeChickenCoreModContainer.loadConfig(); - - if (CodeChickenCoreModContainer.config.getTag("dev.deobfuscate") - .setComment("set to true to completely deobfuscate mcp names") - .getBooleanValue(!ObfMapping.obfuscated)) { - run = new ObfuscationRun(false, ObfMapping.MCPRemapper.getConfFiles(), - ObfuscationRun.fillDefaults(new HashMap())); - run.obf.setHeirachyEvaluator(instance); - run.setQuiet().parseMappings(); - Collections.addAll(excludedPackages, run.config.get("excludedPackages").split(";")); - - if (ObfMapping.obfuscated) { - ObfMapping.loadMCPRemapper(); - run.setSeargeConstants(); - getTransformers().add(instance); - } else { - getTransformers().add(0, instance);//insert transformer as first. - } + run = new ObfuscationRun( + false, + ObfMapping.MCPRemapper.getConfFiles(), + ObfuscationRun.fillDefaults(new HashMap())); + run.obf.setHeirachyEvaluator(instance); + run.setQuiet().parseMappings(); + Collections.addAll(excludedPackages, run.config.get("excludedPackages").split(";")); + + if (ObfMapping.obfuscated) { + ObfMapping.loadMCPRemapper(); + run.setSeargeConstants(); + getTransformers().add(instance); + } else { + getTransformers().add(0, instance); // insert transformer as first. } } @@ -141,8 +137,7 @@ public byte[] transform(String name, String transformedName, byte[] bytes) { activated = true; } - if (!activated || bytes == null) - return bytes; + if (!activated || bytes == null) return bytes; ClassNode cnode = ASMHelper.createClassNode(bytes, ClassReader.EXPAND_FRAMES); ClassWriter cw = new CC_ClassWriter(0, true); @@ -153,21 +148,24 @@ public byte[] transform(String name, String transformedName, byte[] bytes) { private byte[] injectCallback(byte[] bytes) { ClassNode cnode = ASMHelper.createClassNode(bytes); MethodNode mnode = ASMHelper.findMethod(new ObfMapping(cnode.name, "", "()V"), cnode); - mnode.instructions.insert(new MethodInsnNode(INVOKESTATIC, "codechicken/core/asm/MCPDeobfuscationTransformer", "loadCallback", "()V")); + mnode.instructions.insert( + new MethodInsnNode( + INVOKESTATIC, + "codechicken/core/asm/MCPDeobfuscationTransformer", + "loadCallback", + "()V")); return ASMHelper.createBytes(cnode, 0); } public static void loadCallback() { if (ObfMapping.obfuscated) { - //move ourselves to the end + // move ourselves to the end List transformers = getTransformers(); transformers.remove(instance); transformers.add(instance); } else { - //remap access transformers - for (IClassTransformer t : getTransformers()) - if (t instanceof AccessTransformer) - remapAccessTransformer(t); + // remap access transformers + for (IClassTransformer t : getTransformers()) if (t instanceof AccessTransformer) remapAccessTransformer(t); } } @@ -207,10 +205,9 @@ public List getParents(ObfuscationEntry desc) { name = name.replace('/', '.'); try { byte[] bytes = Launch.classLoader.getClassBytes(name); - if (bytes != null) - return ObfuscationRun.getParents(ASMHelper.createClassNode(bytes)); + if (bytes != null) return ObfuscationRun.getParents(ASMHelper.createClassNode(bytes)); } catch (IOException ignored) {} - //clear the miss cache because the library containing it might be loaded later + // clear the miss cache because the library containing it might be loaded later Launch.classLoader.clearNegativeEntries(Collections.singleton(name)); return null; } @@ -218,19 +215,15 @@ public List getParents(ObfuscationEntry desc) { @Override public boolean isLibClass(ObfuscationEntry desc) { String name = desc.srg.s_owner; - for (String p : excludedPackages) - if (name.startsWith(p)) - return true; + for (String p : excludedPackages) if (name.startsWith(p)) return true; return false; } public static String unmap(String name) { - if (run == null) - return null; + if (run == null) return null; ObfuscationEntry e = run.obf.lookupMcpClass(name); - if (e == null) - return null; + if (e == null) return null; return e.obf.s_owner; } diff --git a/src/codechicken/core/asm/MethodASMifier.java b/src/main/java/codechicken/core/asm/MethodASMifier.java similarity index 85% rename from src/codechicken/core/asm/MethodASMifier.java rename to src/main/java/codechicken/core/asm/MethodASMifier.java index 8a40d79..6880a18 100644 --- a/src/codechicken/core/asm/MethodASMifier.java +++ b/src/main/java/codechicken/core/asm/MethodASMifier.java @@ -1,22 +1,22 @@ package codechicken.core.asm; +import static org.objectweb.asm.Opcodes.ASM4; + import java.io.File; import java.io.PrintWriter; import net.minecraft.launchwrapper.Launch; + import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.util.Printer; import org.objectweb.asm.util.TraceMethodVisitor; -import codechicken.core.launch.CodeChickenCorePlugin; import codechicken.lib.asm.ObfMapping; -import static org.objectweb.asm.Opcodes.*; +public class MethodASMifier extends ClassVisitor { -public class MethodASMifier extends ClassVisitor -{ PrintWriter printWriter; ObfMapping method; Printer asmifier; @@ -55,10 +55,8 @@ public static void printMethod(ObfMapping method, Printer printer, File toFile) public static void printMethod(ObfMapping method, byte[] bytes, Printer printer, File toFile) { try { - if (!toFile.getParentFile().exists()) - toFile.getParentFile().mkdirs(); - if (!toFile.exists()) - toFile.createNewFile(); + if (!toFile.getParentFile().exists()) toFile.getParentFile().mkdirs(); + if (!toFile.exists()) toFile.createNewFile(); PrintWriter printWriter = new PrintWriter(toFile); diff --git a/src/main/java/codechicken/core/asm/TweakTransformer.java b/src/main/java/codechicken/core/asm/TweakTransformer.java new file mode 100644 index 0000000..02eb141 --- /dev/null +++ b/src/main/java/codechicken/core/asm/TweakTransformer.java @@ -0,0 +1,119 @@ +package codechicken.core.asm; + +import static codechicken.lib.asm.InsnComparator.findOnce; + +import java.util.Map; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.Launch; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.MethodNode; + +import codechicken.core.launch.CodeChickenCorePlugin; +import codechicken.lib.asm.ASMBlock; +import codechicken.lib.asm.ASMReader; +import codechicken.lib.asm.ModularASMTransformer; +import codechicken.lib.asm.ModularASMTransformer.MethodReplacer; +import codechicken.lib.asm.ModularASMTransformer.MethodTransformer; +import codechicken.lib.asm.ModularASMTransformer.MethodWriter; +import codechicken.lib.asm.ObfMapping; +import codechicken.lib.config.ConfigTag; +import cpw.mods.fml.relauncher.FMLRelaunchLog; + +public class TweakTransformer implements IClassTransformer, Opcodes { + + private static final ModularASMTransformer transformer = new ModularASMTransformer(); + private static final Map blocks = ASMReader + .loadResource("/assets/codechickencore/asm/tweaks.asm"); + public static ConfigTag tweaks; + + public static void load() { + loadTransformersFromConfig(); + if (transformer.isEmpty()) { + CodeChickenCorePlugin.logger + .debug("No tweaks parsed from config, skipping registration of TweakTransformer."); + return; + } + String name = TweakTransformer.class.getName(); + FMLRelaunchLog.finer("Registering transformer %s", name); + Launch.classLoader.registerTransformer(name); + } + + private static void loadTransformersFromConfig() { + tweaks = CodeChickenCorePlugin.config.getTag("tweaks") + .setComment("Various tweaks that can be applied to game mechanics.").useBraces(); + tweaks.removeTag("persistantLava"); + + if (tweaks.getTag("environmentallyFriendlyCreepers").setComment( + "If set to true, creepers will not destroy landscape. (A version of mobGriefing setting just for creepers)") + .getBooleanValue(false)) { + transformer.add( + new MethodReplacer( + new ObfMapping("net/minecraft/entity/monster/EntityCreeper", "func_146077_cc", "()V"), + blocks.get("d_environmentallyFriendlyCreepers"), + blocks.get("environmentallyFriendlyCreepers"))); + } + + if (!tweaks.getTag("softLeafReplace").setComment("If set to false, leaves will only replace air when growing") + .getBooleanValue(false)) { + transformer.add( + new MethodWriter( + ACC_PUBLIC, + new ObfMapping( + "net/minecraft/block/Block", + "canBeReplacedByLeaves", + "(Lnet/minecraft/world/IBlockAccess;III)Z"), + blocks.get("softLeafReplace"))); + } + + if (tweaks.getTag("doFireTickOut").setComment( + "If set to true and doFireTick is disabled in the game rules, fire will still dissipate if it's not over a fire source") + .getBooleanValue(true)) { + transformer.add( + new MethodTransformer( + new ObfMapping( + "net/minecraft/block/BlockFire", + "func_149674_a", + "(Lnet/minecraft/world/World;IIILjava/util/Random;)V")) { + + @Override + public void transform(MethodNode mv) { + ASMBlock needle = blocks.get("n_doFireTick"); + ASMBlock inject = blocks.get("doFireTick"); + + ASMBlock key = needle.applyLabels(findOnce(mv.instructions, needle.list)); + LabelNode jlabel = key.get("LRET"); + mv.instructions.insertBefore(jlabel, new JumpInsnNode(GOTO, inject.get("LSKIP"))); + mv.instructions.insert(jlabel, inject.list.list); + } + }); + } + + if (tweaks.getTag("finiteWater") + .setComment("If set to true two adjacent water source blocks will not generate a third.") + .getBooleanValue(false)) { + transformer.add( + new MethodTransformer( + new ObfMapping( + "net/minecraft/block/BlockDynamicLiquid", + "func_149674_a", + "(Lnet/minecraft/world/World;IIILjava/util/Random;)V")) { + + @Override + public void transform(MethodNode mv) { + ASMBlock needle = blocks.get("finiteWater"); + ASMBlock key = needle.applyLabels(findOnce(mv.instructions, needle.list)); + mv.instructions.insertBefore(key.list.getFirst(), new JumpInsnNode(GOTO, key.get("LEND"))); + } + }); + } + } + + @Override + public final byte[] transform(String name, String tname, byte[] bytes) { + return transformer.transform(name, bytes); + } +} diff --git a/src/codechicken/core/commands/CoreCommand.java b/src/main/java/codechicken/core/commands/CoreCommand.java similarity index 83% rename from src/codechicken/core/commands/CoreCommand.java rename to src/main/java/codechicken/core/commands/CoreCommand.java index aaf30c4..9d4e3d8 100644 --- a/src/codechicken/core/commands/CoreCommand.java +++ b/src/main/java/codechicken/core/commands/CoreCommand.java @@ -2,24 +2,25 @@ import java.util.List; -import codechicken.core.ServerUtils; +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.server.MinecraftServer; import net.minecraft.util.ChatComponentText; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.ChunkCoordinates; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.command.ICommand; -import net.minecraft.command.ICommandSender; import net.minecraft.util.IChatComponent; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import net.minecraftforge.common.DimensionManager; -public abstract class CoreCommand implements ICommand -{ - public class WCommandSender implements ICommandSender - { +import codechicken.core.ServerUtils; + +public abstract class CoreCommand implements ICommand { + + public class WCommandSender implements ICommandSender { + public ICommandSender wrapped; public WCommandSender(ICommandSender sender) { @@ -78,15 +79,13 @@ public String getCommandUsage(ICommandSender var1) { public void processCommand(ICommandSender listener, String[] args) { WCommandSender wsender = new WCommandSender(listener); - if (args.length < minimumParameters() || - args.length == 1 && args[0].equals("help")) { + if (args.length < minimumParameters() || args.length == 1 && args[0].equals("help")) { printHelp(wsender); return; } String command = getCommandName(); - for (String arg : args) - command += " " + arg; + for (String arg : args) command += " " + arg; handleCommand(command, wsender.getCommandSenderName(), args, wsender); } @@ -113,12 +112,12 @@ public int compareTo(Object arg0) { } @Override - public List getCommandAliases() { + public List getCommandAliases() { return null; } @Override - public List addTabCompletionOptions(ICommandSender var1, String[] var2) { + public List addTabCompletionOptions(ICommandSender var1, String[] var2) { return null; } @@ -130,16 +129,13 @@ public boolean isUsernameIndex(String[] astring, int i) { @Override public boolean canCommandSenderUseCommand(ICommandSender var1) { if (OPOnly()) { - if (var1 instanceof EntityPlayer) - return MinecraftServer.getServer().getConfigurationManager().func_152596_g(((EntityPlayer) var1).getGameProfile()); - else if (var1 instanceof MinecraftServer) - return true; - else - return false; + if (var1 instanceof EntityPlayer) return MinecraftServer.getServer().getConfigurationManager() + .func_152596_g(((EntityPlayer) var1).getGameProfile()); + else if (var1 instanceof MinecraftServer) return true; + else return false; } return true; } - public abstract int minimumParameters(); } diff --git a/src/codechicken/core/commands/PlayerCommand.java b/src/main/java/codechicken/core/commands/PlayerCommand.java similarity index 67% rename from src/codechicken/core/commands/PlayerCommand.java rename to src/main/java/codechicken/core/commands/PlayerCommand.java index 1b42cd5..4426344 100644 --- a/src/codechicken/core/commands/PlayerCommand.java +++ b/src/main/java/codechicken/core/commands/PlayerCommand.java @@ -1,60 +1,54 @@ package codechicken.core.commands; -import net.minecraft.util.MovingObjectPosition.MovingObjectType; -import net.minecraft.world.ChunkPosition; +import net.minecraft.command.ICommandSender; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.command.ICommandSender; import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.MovingObjectPosition.MovingObjectType; import net.minecraft.util.Vec3; +import net.minecraft.world.ChunkPosition; import net.minecraft.world.WorldServer; -public abstract class PlayerCommand extends CoreCommand -{ +public abstract class PlayerCommand extends CoreCommand { + @Override - public boolean canCommandSenderUseCommand(ICommandSender var1) - { - if(!super.canCommandSenderUseCommand(var1)) - return false; + public boolean canCommandSenderUseCommand(ICommandSender var1) { + if (!super.canCommandSenderUseCommand(var1)) return false; return var1 instanceof EntityPlayer; } - - + @Override - public void handleCommand(String command, String playername, String[] args, WCommandSender listener) - { - EntityPlayerMP player = (EntityPlayerMP)listener.wrapped; + public void handleCommand(String command, String playername, String[] args, WCommandSender listener) { + EntityPlayerMP player = (EntityPlayerMP) listener.wrapped; handleCommand(getWorld(player), player, args); } - + public abstract void handleCommand(WorldServer world, EntityPlayerMP player, String[] args); - - public ChunkPosition getPlayerLookingAtBlock(EntityPlayerMP player, float reach) - { - Vec3 vec3d = Vec3.createVectorHelper(player.posX, (player.posY + 1.6200000000000001D) - player.yOffset, player.posZ); + + public ChunkPosition getPlayerLookingAtBlock(EntityPlayerMP player, float reach) { + Vec3 vec3d = Vec3 + .createVectorHelper(player.posX, (player.posY + 1.6200000000000001D) - player.yOffset, player.posZ); Vec3 vec3d1 = player.getLook(1.0F); Vec3 vec3d2 = vec3d.addVector(vec3d1.xCoord * reach, vec3d1.yCoord * reach, vec3d1.zCoord * reach); MovingObjectPosition hit = player.worldObj.rayTraceBlocks(vec3d, vec3d2); - if(hit == null || hit.typeOfHit != MovingObjectType.BLOCK) - { + if (hit == null || hit.typeOfHit != MovingObjectType.BLOCK) { return null; } - + return new ChunkPosition(hit.blockX, hit.blockY, hit.blockZ); } - - public Entity getPlayerLookingAtEntity(EntityPlayerMP player, float reach) - { - Vec3 vec3d = Vec3.createVectorHelper(player.posX, (player.posY + 1.6200000000000001D) - player.yOffset, player.posZ); + + public Entity getPlayerLookingAtEntity(EntityPlayerMP player, float reach) { + Vec3 vec3d = Vec3 + .createVectorHelper(player.posX, (player.posY + 1.6200000000000001D) - player.yOffset, player.posZ); Vec3 vec3d1 = player.getLook(1.0F); Vec3 vec3d2 = vec3d.addVector(vec3d1.xCoord * reach, vec3d1.yCoord * reach, vec3d1.zCoord * reach); MovingObjectPosition hit = player.worldObj.rayTraceBlocks(vec3d, vec3d2); - if(hit == null || hit.typeOfHit != MovingObjectType.ENTITY) - { + if (hit == null || hit.typeOfHit != MovingObjectType.ENTITY) { return null; } - + return hit.entityHit; } } diff --git a/src/codechicken/core/commands/ServerCommand.java b/src/main/java/codechicken/core/commands/ServerCommand.java similarity index 62% rename from src/codechicken/core/commands/ServerCommand.java rename to src/main/java/codechicken/core/commands/ServerCommand.java index ef06809..164485f 100644 --- a/src/codechicken/core/commands/ServerCommand.java +++ b/src/main/java/codechicken/core/commands/ServerCommand.java @@ -1,28 +1,24 @@ package codechicken.core.commands; -import net.minecraft.server.MinecraftServer; import net.minecraft.command.ICommandSender; +import net.minecraft.server.MinecraftServer; + +public abstract class ServerCommand extends CoreCommand { -public abstract class ServerCommand extends CoreCommand -{ @Override - public void processCommand(ICommandSender var1, String[] var2) - { - handleCommand(var2, (MinecraftServer)var1); + public void processCommand(ICommandSender var1, String[] var2) { + handleCommand(var2, (MinecraftServer) var1); } - + @Override - public boolean canCommandSenderUseCommand(ICommandSender var1) - { - if(!super.canCommandSenderUseCommand(var1)) - return false; + public boolean canCommandSenderUseCommand(ICommandSender var1) { + if (!super.canCommandSenderUseCommand(var1)) return false; return var1 instanceof MinecraftServer; } - + public abstract void handleCommand(String[] args, MinecraftServer listener); - public final boolean OPOnly() - { + public final boolean OPOnly() { return false; - } + } } diff --git a/src/codechicken/core/featurehack/EntityRenderHook.java b/src/main/java/codechicken/core/featurehack/EntityRenderHook.java similarity index 70% rename from src/codechicken/core/featurehack/EntityRenderHook.java rename to src/main/java/codechicken/core/featurehack/EntityRenderHook.java index 8e89361..452def2 100644 --- a/src/codechicken/core/featurehack/EntityRenderHook.java +++ b/src/main/java/codechicken/core/featurehack/EntityRenderHook.java @@ -4,10 +4,10 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.World; -public class EntityRenderHook extends Entity -{ - public static interface IRenderCallback - { +public class EntityRenderHook extends Entity { + + public static interface IRenderCallback { + public void render(float frame, int pass); public boolean shouldRenderInPass(int pass); @@ -26,21 +26,17 @@ public EntityRenderHook(World world, double x, double y, double z, IRenderCallba @Override public void onUpdate() { - if (!callback.isValid()) - setDead(); + if (!callback.isValid()) setDead(); } @Override - protected void entityInit() { - } + protected void entityInit() {} @Override - protected void readEntityFromNBT(NBTTagCompound var1) { - } + protected void readEntityFromNBT(NBTTagCompound var1) {} @Override - protected void writeEntityToNBT(NBTTagCompound var1) { - } + protected void writeEntityToNBT(NBTTagCompound var1) {} @Override public boolean shouldRenderInPass(int pass) { diff --git a/src/codechicken/core/featurehack/EntityUpdateHook.java b/src/main/java/codechicken/core/featurehack/EntityUpdateHook.java similarity index 52% rename from src/codechicken/core/featurehack/EntityUpdateHook.java rename to src/main/java/codechicken/core/featurehack/EntityUpdateHook.java index c6890d8..c34a5c4 100644 --- a/src/codechicken/core/featurehack/EntityUpdateHook.java +++ b/src/main/java/codechicken/core/featurehack/EntityUpdateHook.java @@ -4,44 +4,35 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.World; -public class EntityUpdateHook extends Entity -{ - public static interface IUpdateCallback - { +public class EntityUpdateHook extends Entity { + + public static interface IUpdateCallback { + public void onUpdate(); public boolean isValid(); } - + public final IUpdateCallback callback; - public EntityUpdateHook(World world, int x, int y, int z, IUpdateCallback callback) - { + + public EntityUpdateHook(World world, int x, int y, int z, IUpdateCallback callback) { super(world); setPosition(x, y, z); this.callback = callback; } @Override - public void onUpdate() - { - if(!callback.isValid()) - setDead(); - else - callback.onUpdate(); + public void onUpdate() { + if (!callback.isValid()) setDead(); + else callback.onUpdate(); } @Override - protected void entityInit() - { - } + protected void entityInit() {} @Override - protected void readEntityFromNBT(NBTTagCompound var1) - { - } + protected void readEntityFromNBT(NBTTagCompound var1) {} @Override - protected void writeEntityToNBT(NBTTagCompound var1) - { - } + protected void writeEntityToNBT(NBTTagCompound var1) {} } diff --git a/src/codechicken/core/featurehack/FeatureHack.java b/src/main/java/codechicken/core/featurehack/FeatureHack.java similarity index 63% rename from src/codechicken/core/featurehack/FeatureHack.java rename to src/main/java/codechicken/core/featurehack/FeatureHack.java index 0f210d9..d840541 100644 --- a/src/codechicken/core/featurehack/FeatureHack.java +++ b/src/main/java/codechicken/core/featurehack/FeatureHack.java @@ -5,34 +5,28 @@ import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -public class FeatureHack -{ +public class FeatureHack { + private static boolean updateHookEnabled = false; private static boolean renderHookEnabled = false; - - public static void enableUpdateHook() - { - if(updateHookEnabled) - return; - + + public static void enableUpdateHook() { + if (updateHookEnabled) return; + updateHookEnabled = true; - if(FMLCommonHandler.instance().getSide().isClient()) - enableClientUpdateHook(); + if (FMLCommonHandler.instance().getSide().isClient()) enableClientUpdateHook(); } @SideOnly(Side.CLIENT) - public static void enableRenderHook() - { - if(renderHookEnabled) - return; - + public static void enableRenderHook() { + if (renderHookEnabled) return; + renderHookEnabled = true; RenderingRegistry.registerEntityRenderingHandler(EntityUpdateHook.class, new RenderNull()); } @SideOnly(Side.CLIENT) - private static void enableClientUpdateHook() - { + private static void enableClientUpdateHook() { RenderingRegistry.registerEntityRenderingHandler(EntityRenderHook.class, new RenderEntityRenderHook()); } } diff --git a/src/codechicken/core/featurehack/GameDataManipulator.java b/src/main/java/codechicken/core/featurehack/GameDataManipulator.java similarity index 65% rename from src/codechicken/core/featurehack/GameDataManipulator.java rename to src/main/java/codechicken/core/featurehack/GameDataManipulator.java index 87e2dc4..920fcb4 100644 --- a/src/codechicken/core/featurehack/GameDataManipulator.java +++ b/src/main/java/codechicken/core/featurehack/GameDataManipulator.java @@ -1,27 +1,31 @@ package codechicken.core.featurehack; -import codechicken.lib.asm.ObfMapping; +import java.lang.reflect.Field; +import java.util.Map; + import net.minecraft.item.Item; import net.minecraft.util.ObjectIntIdentityMap; import net.minecraft.util.RegistryNamespaced; import net.minecraft.util.RegistrySimple; -import java.lang.reflect.Field; -import java.util.Map; +import codechicken.lib.asm.ObfMapping; + +public class GameDataManipulator { -public class GameDataManipulator -{ public static Field f_registryObjects; public static Field f_underlyingIntegerMap; - static - { + static { try { f_registryObjects = RegistrySimple.class.getDeclaredField( - new ObfMapping("net/minecraft/util/RegistrySimple", "field_82596_a", "Ljava/util/Map;").toRuntime().s_name); + new ObfMapping("net/minecraft/util/RegistrySimple", "field_82596_a", "Ljava/util/Map;") + .toRuntime().s_name); f_registryObjects.setAccessible(true); f_underlyingIntegerMap = RegistryNamespaced.class.getDeclaredField( - new ObfMapping("net/minecraft/util/RegistryNamespaced", "field_148759_a", "Lnet/minecraft/util/ObjectIntIdentityMap;").toRuntime().s_name); + new ObfMapping( + "net/minecraft/util/RegistryNamespaced", + "field_148759_a", + "Lnet/minecraft/util/ObjectIntIdentityMap;").toRuntime().s_name); f_underlyingIntegerMap.setAccessible(true); } catch (Exception e) { throw new RuntimeException(e); @@ -31,8 +35,8 @@ public class GameDataManipulator public static void replaceItem(int id, Item item) { try { String name = Item.itemRegistry.getNameForObject(Item.getItemById(id)); - ((Map)f_registryObjects.get(Item.itemRegistry)).put(name, item); - ((ObjectIntIdentityMap)f_underlyingIntegerMap.get(Item.itemRegistry)).func_148746_a(item, id); + ((Map) f_registryObjects.get(Item.itemRegistry)).put(name, item); + ((ObjectIntIdentityMap) f_underlyingIntegerMap.get(Item.itemRegistry)).func_148746_a(item, id); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/src/codechicken/core/featurehack/LiquidTextures.java b/src/main/java/codechicken/core/featurehack/LiquidTextures.java similarity index 73% rename from src/codechicken/core/featurehack/LiquidTextures.java rename to src/main/java/codechicken/core/featurehack/LiquidTextures.java index 2911c5a..7c531bd 100644 --- a/src/codechicken/core/featurehack/LiquidTextures.java +++ b/src/main/java/codechicken/core/featurehack/LiquidTextures.java @@ -1,58 +1,55 @@ package codechicken.core.featurehack; import java.lang.reflect.Field; -import codechicken.core.ReflectionManager; -import codechicken.core.featurehack.mc.TextureLavaFX; -import codechicken.core.featurehack.mc.TextureLavaFlowFX; -import codechicken.core.featurehack.mc.TextureWaterFX; -import codechicken.core.featurehack.mc.TextureWaterFlowFX; -import cpw.mods.fml.common.eventhandler.SubscribeEvent; -import net.minecraft.block.Block; + import net.minecraft.init.Blocks; import net.minecraft.util.IIcon; import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.common.MinecraftForge; +import codechicken.core.ReflectionManager; import codechicken.core.asm.TweakTransformer; +import codechicken.core.featurehack.mc.TextureLavaFX; +import codechicken.core.featurehack.mc.TextureLavaFlowFX; +import codechicken.core.featurehack.mc.TextureWaterFX; +import codechicken.core.featurehack.mc.TextureWaterFlowFX; import codechicken.lib.asm.ObfMapping; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; + +public class LiquidTextures { -public class LiquidTextures -{ public static IIcon[] newTextures = new IIcon[4]; - + public static boolean replaceLava; public static boolean replaceWater; - + private static Field field_tex; - - public static void init() - { - replaceWater = TweakTransformer.tweaks.getTag("replaceWaterFX").setComment("Set this to true to use the pre1.5 water textures").getBooleanValue(false); - replaceLava = TweakTransformer.tweaks.getTag("replaceLavaFX").setComment("Set this to true to use the pre1.5 lava textures").getBooleanValue(false); - if(replaceWater) - { + + public static void init() { + replaceWater = TweakTransformer.tweaks.getTag("replaceWaterFX") + .setComment("Set this to true to use the pre1.5 water textures").getBooleanValue(false); + replaceLava = TweakTransformer.tweaks.getTag("replaceLavaFX") + .setComment("Set this to true to use the pre1.5 lava textures").getBooleanValue(false); + if (replaceWater) { newTextures[0] = new TextureWaterFX().texture; newTextures[1] = new TextureWaterFlowFX().texture; } - if(replaceLava) - { + if (replaceLava) { newTextures[2] = new TextureLavaFX().texture; newTextures[3] = new TextureLavaFlowFX().texture; } - - if(replaceWater || replaceLava) - { + + if (replaceWater || replaceLava) { MinecraftForge.EVENT_BUS.register(new LiquidTextures()); - field_tex = ReflectionManager.getField(new ObfMapping("net/minecraft/block/BlockLiquid", "field_149806_a", "[Lnet/minecraft/util/IIcon;")); + field_tex = ReflectionManager.getField( + new ObfMapping("net/minecraft/block/BlockLiquid", "field_149806_a", "[Lnet/minecraft/util/IIcon;")); } } @SubscribeEvent - public void postStitch(TextureStitchEvent.Post event) - { + public void postStitch(TextureStitchEvent.Post event) { IIcon[] icons; - if(replaceLava) - { + if (replaceLava) { icons = ReflectionManager.get(field_tex, IIcon[].class, Blocks.flowing_lava); icons[0] = newTextures[2]; icons[1] = newTextures[3]; @@ -60,8 +57,7 @@ public void postStitch(TextureStitchEvent.Post event) icons[0] = newTextures[2]; icons[1] = newTextures[3]; } - if(replaceWater) - { + if (replaceWater) { icons = ReflectionManager.get(field_tex, IIcon[].class, Blocks.flowing_water); icons[0] = newTextures[0]; icons[1] = newTextures[1]; diff --git a/src/main/java/codechicken/core/featurehack/RenderEntityRenderHook.java b/src/main/java/codechicken/core/featurehack/RenderEntityRenderHook.java new file mode 100644 index 0000000..d32f8cd --- /dev/null +++ b/src/main/java/codechicken/core/featurehack/RenderEntityRenderHook.java @@ -0,0 +1,24 @@ +package codechicken.core.featurehack; + +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.MinecraftForgeClient; + +import org.lwjgl.opengl.GL11; + +public class RenderEntityRenderHook extends Render { + + @Override + public void doRender(Entity entity, double x, double y, double z, float f, float frame) { + EntityRenderHook hook = (EntityRenderHook) entity; + GL11.glTranslated(x - hook.posX, y - hook.posY, z - hook.posZ); + ((EntityRenderHook) entity).callback.render(frame, MinecraftForgeClient.getRenderPass()); + GL11.glTranslated(hook.posX - x, hook.posY - y, hook.posZ - z); + } + + @Override + protected ResourceLocation getEntityTexture(Entity entity) { + return null; + } +} diff --git a/src/codechicken/core/featurehack/RenderNull.java b/src/main/java/codechicken/core/featurehack/RenderNull.java similarity index 64% rename from src/codechicken/core/featurehack/RenderNull.java rename to src/main/java/codechicken/core/featurehack/RenderNull.java index e6fc568..a15240d 100644 --- a/src/codechicken/core/featurehack/RenderNull.java +++ b/src/main/java/codechicken/core/featurehack/RenderNull.java @@ -4,16 +4,13 @@ import net.minecraft.entity.Entity; import net.minecraft.util.ResourceLocation; -public class RenderNull extends Render -{ +public class RenderNull extends Render { + @Override - public void doRender(Entity var1, double var2, double var4, double var6, float var8, float var9) - { - } - + public void doRender(Entity var1, double var2, double var4, double var6, float var8, float var9) {} + @Override - protected ResourceLocation getEntityTexture(Entity entity) - { + protected ResourceLocation getEntityTexture(Entity entity) { return null; } } diff --git a/src/codechicken/core/featurehack/TweakTransformerHelper.java b/src/main/java/codechicken/core/featurehack/TweakTransformerHelper.java similarity index 56% rename from src/codechicken/core/featurehack/TweakTransformerHelper.java rename to src/main/java/codechicken/core/featurehack/TweakTransformerHelper.java index 816320b..873aaeb 100644 --- a/src/codechicken/core/featurehack/TweakTransformerHelper.java +++ b/src/main/java/codechicken/core/featurehack/TweakTransformerHelper.java @@ -1,38 +1,37 @@ package codechicken.core.featurehack; +import static net.minecraftforge.common.util.ForgeDirection.UP; + import java.util.Random; import net.minecraft.block.Block; import net.minecraft.init.Blocks; import net.minecraft.world.World; -import static net.minecraftforge.common.util.ForgeDirection.UP; +public class TweakTransformerHelper { -public class TweakTransformerHelper -{ - public static void quenchFireTick(World world, int x, int y, int z, Random rand) - { + public static void quenchFireTick(World world, int x, int y, int z, Random rand) { Block base = world.getBlock(x, y - 1, z); boolean supported = (base != null && base.isFireSource(world, x, y - 1, z, UP)); - - if (!Blocks.fire.canPlaceBlockAt(world, x, y, z) || - !supported && world.isRaining() && (world.canLightningStrikeAt(x, y, z) || world.canLightningStrikeAt(x - 1, y, z) || world.canLightningStrikeAt(x + 1, y, z) || world.canLightningStrikeAt(x, y, z - 1) || world.canLightningStrikeAt(x, y, z + 1))) + + if (!Blocks.fire.canPlaceBlockAt(world, x, y, z) || !supported && world.isRaining() + && (world.canLightningStrikeAt(x, y, z) || world.canLightningStrikeAt(x - 1, y, z) + || world.canLightningStrikeAt(x + 1, y, z) + || world.canLightningStrikeAt(x, y, z - 1) + || world.canLightningStrikeAt(x, y, z + 1))) world.setBlockToAir(x, y, z); - else - { + else { int meta = world.getBlockMetadata(x, y, z); - if(meta < 15) - world.setBlockMetadataWithNotify(x, y, z, meta+rand.nextInt(3)/2, 0); - + if (meta < 15) world.setBlockMetadataWithNotify(x, y, z, meta + rand.nextInt(3) / 2, 0); + world.scheduleBlockUpdate(x, y, z, Blocks.fire, Blocks.fire.tickRate(world) + rand.nextInt(10)); - - if(!supported && !Blocks.fire.canCatchFire(world, x, y - 1, z, UP) && meta == 15 && rand.nextInt(4) == 0) + + if (!supported && !Blocks.fire.canCatchFire(world, x, y - 1, z, UP) && meta == 15 && rand.nextInt(4) == 0) world.setBlockToAir(x, y, z); } } - - public static boolean canPlaceBlockAt(World world, int x, int y, int z) - { + + public static boolean canPlaceBlockAt(World world, int x, int y, int z) { Block block = world.getBlock(x, y, z); return block.isAir(world, x, y, z) || block.isReplaceable(world, x, y, z); } diff --git a/src/codechicken/core/featurehack/mc/TextureLavaFX.java b/src/main/java/codechicken/core/featurehack/mc/TextureLavaFX.java similarity index 58% rename from src/codechicken/core/featurehack/mc/TextureLavaFX.java rename to src/main/java/codechicken/core/featurehack/mc/TextureLavaFX.java index dabbbe3..18f1217 100644 --- a/src/codechicken/core/featurehack/mc/TextureLavaFX.java +++ b/src/main/java/codechicken/core/featurehack/mc/TextureLavaFX.java @@ -1,28 +1,27 @@ package codechicken.core.featurehack.mc; +import net.minecraft.util.MathHelper; + import codechicken.lib.colour.ColourRGBA; import codechicken.lib.render.TextureFX; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import net.minecraft.util.MathHelper; @SideOnly(Side.CLIENT) -public class TextureLavaFX extends TextureFX -{ +public class TextureLavaFX extends TextureFX { + protected float[] field_76876_g = new float[256]; protected float[] field_76878_h = new float[256]; protected float[] field_76879_i = new float[256]; protected float[] field_76877_j = new float[256]; - public TextureLavaFX() - { + public TextureLavaFX() { super(16, "lava_still_fx"); setup(); } @Override - public void setup() - { + public void setup() { super.setup(); field_76876_g = new float[tileSizeSquare]; field_76878_h = new float[tileSizeSquare]; @@ -30,8 +29,7 @@ public void setup() field_76877_j = new float[tileSizeSquare]; } - public void onTick() - { + public void onTick() { int var2; float var3; int var5; @@ -40,36 +38,40 @@ public void onTick() int var8; int var9; - for (int var1 = 0; var1 < tileSizeBase; ++var1) - { - for (var2 = 0; var2 < tileSizeBase; ++var2) - { + for (int var1 = 0; var1 < tileSizeBase; ++var1) { + for (var2 = 0; var2 < tileSizeBase; ++var2) { var3 = 0.0F; - int var4 = (int)(MathHelper.sin(var2 * (float)Math.PI * 2.0F / 16.0F) * 1.2F); - var5 = (int)(MathHelper.sin(var1 * (float)Math.PI * 2.0F / 16.0F) * 1.2F); + int var4 = (int) (MathHelper.sin(var2 * (float) Math.PI * 2.0F / 16.0F) * 1.2F); + var5 = (int) (MathHelper.sin(var1 * (float) Math.PI * 2.0F / 16.0F) * 1.2F); - for (var6 = var1 - 1; var6 <= var1 + 1; ++var6) - { - for (var7 = var2 - 1; var7 <= var2 + 1; ++var7) - { + for (var6 = var1 - 1; var6 <= var1 + 1; ++var6) { + for (var7 = var2 - 1; var7 <= var2 + 1; ++var7) { var8 = var6 + var4 & tileSizeMask; var9 = var7 + var5 & tileSizeMask; var3 += this.field_76876_g[var8 + var9 * tileSizeBase]; } } - this.field_76878_h[var1 + var2 * tileSizeBase] = var3 / 10.0F + (this.field_76879_i[(var1 + 0 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + this.field_76879_i[(var1 + 1 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + this.field_76879_i[(var1 + 1 & tileSizeMask) + (var2 + 1 & tileSizeMask) * tileSizeBase] + this.field_76879_i[(var1 + 0 & tileSizeMask) + (var2 + 1 & tileSizeMask) * tileSizeBase]) / 4.0F * 0.8F; - this.field_76879_i[var1 + var2 * tileSizeBase] += this.field_76877_j[var1 + var2 * tileSizeBase] * 0.01F; - - if (this.field_76879_i[var1 + var2 * tileSizeBase] < 0.0F) - { + this.field_76878_h[var1 + var2 * tileSizeBase] = var3 / 10.0F + + (this.field_76879_i[(var1 + 0 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + + this.field_76879_i[(var1 + 1 & tileSizeMask) + + (var2 + 0 & tileSizeMask) * tileSizeBase] + + this.field_76879_i[(var1 + 1 & tileSizeMask) + + (var2 + 1 & tileSizeMask) * tileSizeBase] + + this.field_76879_i[(var1 + 0 & tileSizeMask) + + (var2 + 1 & tileSizeMask) * tileSizeBase]) + / 4.0F + * 0.8F; + this.field_76879_i[var1 + var2 * tileSizeBase] += this.field_76877_j[var1 + var2 * tileSizeBase] + * 0.01F; + + if (this.field_76879_i[var1 + var2 * tileSizeBase] < 0.0F) { this.field_76879_i[var1 + var2 * tileSizeBase] = 0.0F; } this.field_76877_j[var1 + var2 * tileSizeBase] -= 0.06F; - if (Math.random() < 0.005D) - { + if (Math.random() < 0.005D) { this.field_76877_j[var1 + var2 * tileSizeBase] = 1.5F; } } @@ -79,26 +81,22 @@ public void onTick() this.field_76878_h = this.field_76876_g; this.field_76876_g = var11; - for (var2 = 0; var2 < tileSizeSquare; ++var2) - { + for (var2 = 0; var2 < tileSizeSquare; ++var2) { var3 = this.field_76876_g[var2] * 2.0F; - if (var3 > 1.0F) - { + if (var3 > 1.0F) { var3 = 1.0F; } - if (var3 < 0.0F) - { + if (var3 < 0.0F) { var3 = 0.0F; } - var5 = (int)(var3 * 100.0F + 155.0F); - var6 = (int)(var3 * var3 * 255.0F); - var7 = (int)(var3 * var3 * var3 * var3 * 128.0F); + var5 = (int) (var3 * 100.0F + 155.0F); + var6 = (int) (var3 * var3 * 255.0F); + var7 = (int) (var3 * var3 * var3 * var3 * 128.0F); - if (this.anaglyphEnabled) - { + if (this.anaglyphEnabled) { var8 = (var5 * 30 + var6 * 59 + var7 * 11) / 100; var9 = (var5 * 30 + var6 * 70) / 100; int var10 = (var5 * 30 + var7 * 70) / 100; diff --git a/src/codechicken/core/featurehack/mc/TextureLavaFlowFX.java b/src/main/java/codechicken/core/featurehack/mc/TextureLavaFlowFX.java similarity index 56% rename from src/codechicken/core/featurehack/mc/TextureLavaFlowFX.java rename to src/main/java/codechicken/core/featurehack/mc/TextureLavaFlowFX.java index b1912d7..37d58eb 100644 --- a/src/codechicken/core/featurehack/mc/TextureLavaFlowFX.java +++ b/src/main/java/codechicken/core/featurehack/mc/TextureLavaFlowFX.java @@ -1,34 +1,33 @@ package codechicken.core.featurehack.mc; +import net.minecraft.util.MathHelper; + import codechicken.lib.colour.ColourRGBA; import codechicken.lib.render.TextureFX; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import net.minecraft.util.MathHelper; @SideOnly(Side.CLIENT) -public class TextureLavaFlowFX extends TextureFX -{ +public class TextureLavaFlowFX extends TextureFX { + protected float[] field_76871_g = new float[256]; protected float[] field_76874_h = new float[256]; protected float[] field_76875_i = new float[256]; protected float[] field_76872_j = new float[256]; int field_76873_k = 0; - //shadow + // shadow public int tileSizeBase = 16; public int tileSizeSquare = 256; public int tileSizeMask = 15; public int tileSizeSquareMask = 255; - public TextureLavaFlowFX() - { + public TextureLavaFlowFX() { super(32, "lava_flow_fx"); } @Override - public void setup() - { + public void setup() { super.setup(); field_76871_g = new float[tileSizeSquare]; field_76874_h = new float[tileSizeSquare]; @@ -37,8 +36,7 @@ public void setup() field_76873_k = 0; } - public void onTick() - { + public void onTick() { ++this.field_76873_k; int var2; float var3; @@ -48,36 +46,40 @@ public void onTick() int var8; int var9; - for (int var1 = 0; var1 < tileSizeBase; ++var1) - { - for (var2 = 0; var2 < tileSizeBase; ++var2) - { + for (int var1 = 0; var1 < tileSizeBase; ++var1) { + for (var2 = 0; var2 < tileSizeBase; ++var2) { var3 = 0.0F; - int var4 = (int)(MathHelper.sin(var2 * (float)Math.PI * 2.0F / 16.0F) * 1.2F); - var5 = (int)(MathHelper.sin(var1 * (float)Math.PI * 2.0F / 16.0F) * 1.2F); + int var4 = (int) (MathHelper.sin(var2 * (float) Math.PI * 2.0F / 16.0F) * 1.2F); + var5 = (int) (MathHelper.sin(var1 * (float) Math.PI * 2.0F / 16.0F) * 1.2F); - for (var6 = var1 - 1; var6 <= var1 + 1; ++var6) - { - for (var7 = var2 - 1; var7 <= var2 + 1; ++var7) - { + for (var6 = var1 - 1; var6 <= var1 + 1; ++var6) { + for (var7 = var2 - 1; var7 <= var2 + 1; ++var7) { var8 = var6 + var4 & tileSizeMask; var9 = var7 + var5 & tileSizeMask; var3 += this.field_76871_g[var8 + var9 * tileSizeBase]; } } - this.field_76874_h[var1 + var2 * tileSizeBase] = var3 / 10.0F + (this.field_76875_i[(var1 + 0 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + this.field_76875_i[(var1 + 1 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + this.field_76875_i[(var1 + 1 & tileSizeMask) + (var2 + 1 & tileSizeMask) * tileSizeBase] + this.field_76875_i[(var1 + 0 & tileSizeMask) + (var2 + 1 & tileSizeMask) * tileSizeBase]) / 4.0F * 0.8F; - this.field_76875_i[var1 + var2 * tileSizeBase] += this.field_76872_j[var1 + var2 * tileSizeBase] * 0.01F; - - if (this.field_76875_i[var1 + var2 * tileSizeBase] < 0.0F) - { + this.field_76874_h[var1 + var2 * tileSizeBase] = var3 / 10.0F + + (this.field_76875_i[(var1 + 0 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + + this.field_76875_i[(var1 + 1 & tileSizeMask) + + (var2 + 0 & tileSizeMask) * tileSizeBase] + + this.field_76875_i[(var1 + 1 & tileSizeMask) + + (var2 + 1 & tileSizeMask) * tileSizeBase] + + this.field_76875_i[(var1 + 0 & tileSizeMask) + + (var2 + 1 & tileSizeMask) * tileSizeBase]) + / 4.0F + * 0.8F; + this.field_76875_i[var1 + var2 * tileSizeBase] += this.field_76872_j[var1 + var2 * tileSizeBase] + * 0.01F; + + if (this.field_76875_i[var1 + var2 * tileSizeBase] < 0.0F) { this.field_76875_i[var1 + var2 * tileSizeBase] = 0.0F; } this.field_76872_j[var1 + var2 * tileSizeBase] -= 0.06F; - if (Math.random() < 0.005D) - { + if (Math.random() < 0.005D) { this.field_76872_j[var1 + var2 * tileSizeBase] = 1.5F; } } @@ -87,26 +89,22 @@ public void onTick() this.field_76874_h = this.field_76871_g; this.field_76871_g = var11; - for (var2 = 0; var2 < tileSizeSquare; ++var2) - { + for (var2 = 0; var2 < tileSizeSquare; ++var2) { var3 = this.field_76871_g[(var2 - this.field_76873_k / 3 * tileSizeBase) & tileSizeSquareMask] * 2.0F; - if (var3 > 1.0F) - { + if (var3 > 1.0F) { var3 = 1.0F; } - if (var3 < 0.0F) - { + if (var3 < 0.0F) { var3 = 0.0F; } - var5 = (int)(var3 * 100.0F + 155.0F); - var6 = (int)(var3 * var3 * 255.0F); - var7 = (int)(var3 * var3 * var3 * var3 * 128.0F); + var5 = (int) (var3 * 100.0F + 155.0F); + var6 = (int) (var3 * var3 * 255.0F); + var7 = (int) (var3 * var3 * var3 * var3 * 128.0F); - if (this.anaglyphEnabled) - { + if (this.anaglyphEnabled) { var8 = (var5 * 30 + var6 * 59 + var7 * 11) / 100; var9 = (var5 * 30 + var6 * 70) / 100; int var10 = (var5 * 30 + var7 * 70) / 100; @@ -115,17 +113,16 @@ public void onTick() var7 = var10; } - int px = var2&tileSizeMask; - int py = var2/tileSizeBase; + int px = var2 & tileSizeMask; + int py = var2 / tileSizeBase; writeColour(px, py, var5, var6, var7, -1); - writeColour(px+16, py, var5, var6, var7, -1); - writeColour(px, py+16, var5, var6, var7, -1); - writeColour(px+16, py+16, var5, var6, var7, -1); + writeColour(px + 16, py, var5, var6, var7, -1); + writeColour(px, py + 16, var5, var6, var7, -1); + writeColour(px + 16, py + 16, var5, var6, var7, -1); } } - private void writeColour(int px, int py, int var5, int var6, int var7, int var8) - { - imageData[py*32+px] = new ColourRGBA(var5, var6, var7, var8).argb(); + private void writeColour(int px, int py, int var5, int var6, int var7, int var8) { + imageData[py * 32 + px] = new ColourRGBA(var5, var6, var7, var8).argb(); } } diff --git a/src/codechicken/core/featurehack/mc/TextureWaterFX.java b/src/main/java/codechicken/core/featurehack/mc/TextureWaterFX.java similarity index 74% rename from src/codechicken/core/featurehack/mc/TextureWaterFX.java rename to src/main/java/codechicken/core/featurehack/mc/TextureWaterFX.java index bb62c42..bed6518 100644 --- a/src/codechicken/core/featurehack/mc/TextureWaterFX.java +++ b/src/main/java/codechicken/core/featurehack/mc/TextureWaterFX.java @@ -6,8 +6,8 @@ import cpw.mods.fml.relauncher.SideOnly; @SideOnly(Side.CLIENT) -public class TextureWaterFX extends TextureFX -{ +public class TextureWaterFX extends TextureFX { + /** red RGB value for water texture */ protected float[] red = new float[256]; @@ -19,15 +19,14 @@ public class TextureWaterFX extends TextureFX /** alpha RGB value for water texture */ protected float[] alpha = new float[256]; - public TextureWaterFX() - { + + public TextureWaterFX() { super(16, "water_still_fx"); setup(); } @Override - public void setup() - { + public void setup() { super.setup(); red = new float[tileSizeSquare]; green = new float[tileSizeSquare]; @@ -35,22 +34,18 @@ public void setup() alpha = new float[tileSizeSquare]; } - public void onTick() - { + public void onTick() { int var1; int var2; float var3; int var5; int var6; - for (var1 = 0; var1 < tileSizeBase; ++var1) - { - for (var2 = 0; var2 < tileSizeBase; ++var2) - { + for (var1 = 0; var1 < tileSizeBase; ++var1) { + for (var2 = 0; var2 < tileSizeBase; ++var2) { var3 = 0.0F; - for (int var4 = var1 - 1; var4 <= var1 + 1; ++var4) - { + for (int var4 = var1 - 1; var4 <= var1 + 1; ++var4) { var5 = var4 & tileSizeMask; var6 = var2 & tileSizeMask; var3 += this.red[var5 + var6 * tileSizeBase]; @@ -60,21 +55,17 @@ public void onTick() } } - for (var1 = 0; var1 < tileSizeBase; ++var1) - { - for (var2 = 0; var2 < tileSizeBase; ++var2) - { + for (var1 = 0; var1 < tileSizeBase; ++var1) { + for (var2 = 0; var2 < tileSizeBase; ++var2) { this.blue[var1 + var2 * tileSizeBase] += this.alpha[var1 + var2 * tileSizeBase] * 0.05F; - if (this.blue[var1 + var2 * tileSizeBase] < 0.0F) - { + if (this.blue[var1 + var2 * tileSizeBase] < 0.0F) { this.blue[var1 + var2 * tileSizeBase] = 0.0F; } this.alpha[var1 + var2 * tileSizeBase] -= 0.1F; - if (Math.random() < 0.05D) - { + if (Math.random() < 0.05D) { this.alpha[var1 + var2 * tileSizeBase] = 0.5F; } } @@ -84,28 +75,24 @@ public void onTick() this.green = this.red; this.red = var12; - for (var2 = 0; var2 < tileSizeSquare; ++var2) - { + for (var2 = 0; var2 < tileSizeSquare; ++var2) { var3 = this.red[var2]; - if (var3 > 1.0F) - { + if (var3 > 1.0F) { var3 = 1.0F; } - if (var3 < 0.0F) - { + if (var3 < 0.0F) { var3 = 0.0F; } float var13 = var3 * var3; - var5 = (int)(32.0F + var13 * 32.0F); - var6 = (int)(50.0F + var13 * 64.0F); + var5 = (int) (32.0F + var13 * 32.0F); + var6 = (int) (50.0F + var13 * 64.0F); int var7 = 255; - int var8 = (int)(146.0F + var13 * 50.0F); + int var8 = (int) (146.0F + var13 * 50.0F); - if (this.anaglyphEnabled) - { + if (this.anaglyphEnabled) { int var9 = (var5 * 30 + var6 * 59 + var7 * 11) / 100; int var10 = (var5 * 30 + var6 * 70) / 100; int var11 = (var5 * 30 + var7 * 70) / 100; diff --git a/src/codechicken/core/featurehack/mc/TextureWaterFlowFX.java b/src/main/java/codechicken/core/featurehack/mc/TextureWaterFlowFX.java similarity index 64% rename from src/codechicken/core/featurehack/mc/TextureWaterFlowFX.java rename to src/main/java/codechicken/core/featurehack/mc/TextureWaterFlowFX.java index d6b92cd..fb03e0d 100644 --- a/src/codechicken/core/featurehack/mc/TextureWaterFlowFX.java +++ b/src/main/java/codechicken/core/featurehack/mc/TextureWaterFlowFX.java @@ -6,28 +6,26 @@ import cpw.mods.fml.relauncher.SideOnly; @SideOnly(Side.CLIENT) -public class TextureWaterFlowFX extends TextureFX -{ +public class TextureWaterFlowFX extends TextureFX { + protected float[] field_76880_g = new float[256]; protected float[] field_76883_h = new float[256]; protected float[] field_76884_i = new float[256]; protected float[] field_76881_j = new float[256]; private int tickCounter = 0; - - //shadow + + // shadow public int tileSizeBase = 16; public int tileSizeSquare = 256; public int tileSizeMask = 15; public int tileSizeSquareMask = 255; - public TextureWaterFlowFX() - { + public TextureWaterFlowFX() { super(32, "water_flow_fx"); } @Override - public void setup() - { + public void setup() { super.setup(); field_76880_g = new float[tileSizeSquare]; field_76883_h = new float[tileSizeSquare]; @@ -36,8 +34,7 @@ public void setup() tickCounter = 0; } - public void onTick() - { + public void onTick() { ++this.tickCounter; int var1; int var2; @@ -45,38 +42,33 @@ public void onTick() int var5; int var6; - for (var1 = 0; var1 < tileSizeBase; ++var1) - { - for (var2 = 0; var2 < tileSizeBase; ++var2) - { + for (var1 = 0; var1 < tileSizeBase; ++var1) { + for (var2 = 0; var2 < tileSizeBase; ++var2) { var3 = 0.0F; - for (int var4 = var2 - 2; var4 <= var2; ++var4) - { + for (int var4 = var2 - 2; var4 <= var2; ++var4) { var5 = var1 & tileSizeMask; var6 = var4 & tileSizeMask; var3 += this.field_76880_g[var5 + var6 * tileSizeBase]; } - this.field_76883_h[var1 + var2 * tileSizeBase] = var3 / 3.2F + this.field_76884_i[var1 + var2 * tileSizeBase] * 0.8F; + this.field_76883_h[var1 + var2 * tileSizeBase] = var3 / 3.2F + + this.field_76884_i[var1 + var2 * tileSizeBase] * 0.8F; } } - for (var1 = 0; var1 < tileSizeBase; ++var1) - { - for (var2 = 0; var2 < tileSizeBase; ++var2) - { - this.field_76884_i[var1 + var2 * tileSizeBase] += this.field_76881_j[var1 + var2 * tileSizeBase] * 0.05F; + for (var1 = 0; var1 < tileSizeBase; ++var1) { + for (var2 = 0; var2 < tileSizeBase; ++var2) { + this.field_76884_i[var1 + var2 * tileSizeBase] += this.field_76881_j[var1 + var2 * tileSizeBase] + * 0.05F; - if (this.field_76884_i[var1 + var2 * tileSizeBase] < 0.0F) - { + if (this.field_76884_i[var1 + var2 * tileSizeBase] < 0.0F) { this.field_76884_i[var1 + var2 * tileSizeBase] = 0.0F; } this.field_76881_j[var1 + var2 * tileSizeBase] -= 0.3F; - if (Math.random() < 0.2D) - { + if (Math.random() < 0.2D) { this.field_76881_j[var1 + var2 * tileSizeBase] = 0.5F; } } @@ -86,28 +78,24 @@ public void onTick() this.field_76883_h = this.field_76880_g; this.field_76880_g = var12; - for (var2 = 0; var2 < tileSizeSquare; ++var2) - { + for (var2 = 0; var2 < tileSizeSquare; ++var2) { var3 = this.field_76880_g[var2 - this.tickCounter * tileSizeBase & tileSizeSquareMask]; - if (var3 > 1.0F) - { + if (var3 > 1.0F) { var3 = 1.0F; } - if (var3 < 0.0F) - { + if (var3 < 0.0F) { var3 = 0.0F; } float var13 = var3 * var3; - var5 = (int)(32.0F + var13 * 32.0F); - var6 = (int)(50.0F + var13 * 64.0F); + var5 = (int) (32.0F + var13 * 32.0F); + var6 = (int) (50.0F + var13 * 64.0F); int var7 = 255; - int var8 = (int)(146.0F + var13 * 50.0F); + int var8 = (int) (146.0F + var13 * 50.0F); - if (this.anaglyphEnabled) - { + if (this.anaglyphEnabled) { int var9 = (var5 * 30 + var6 * 59 + var7 * 11) / 100; int var10 = (var5 * 30 + var6 * 70) / 100; int var11 = (var5 * 30 + var7 * 70) / 100; @@ -116,18 +104,16 @@ public void onTick() var7 = var11; } - - int px = var2&tileSizeMask; - int py = var2/tileSizeBase; + int px = var2 & tileSizeMask; + int py = var2 / tileSizeBase; writeColour(px, py, var5, var6, var7, var8); - writeColour(px+16, py, var5, var6, var7, var8); - writeColour(px, py+16, var5, var6, var7, var8); - writeColour(px+16, py+16, var5, var6, var7, var8); + writeColour(px + 16, py, var5, var6, var7, var8); + writeColour(px, py + 16, var5, var6, var7, var8); + writeColour(px + 16, py + 16, var5, var6, var7, var8); } } - private void writeColour(int px, int py, int var5, int var6, int var7, int var8) - { - imageData[py*32+px] = new ColourRGBA(var5, var6, var7, var8).argb(); + private void writeColour(int px, int py, int var5, int var6, int var7, int var8) { + imageData[py * 32 + px] = new ColourRGBA(var5, var6, var7, var8).argb(); } } diff --git a/src/main/java/codechicken/core/fluid/ExtendedFluidTank.java b/src/main/java/codechicken/core/fluid/ExtendedFluidTank.java new file mode 100644 index 0000000..b40642a --- /dev/null +++ b/src/main/java/codechicken/core/fluid/ExtendedFluidTank.java @@ -0,0 +1,95 @@ +package codechicken.core.fluid; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidTankInfo; +import net.minecraftforge.fluids.IFluidTank; + +public class ExtendedFluidTank implements IFluidTank { + + private FluidStack fluid; + private boolean changeType; + private int capacity; + + public ExtendedFluidTank(FluidStack type, int capacity) { + if (type == null) { + type = FluidUtils.water; + changeType = true; + } + + fluid = FluidUtils.copy(type, 0); + this.capacity = capacity; + } + + public ExtendedFluidTank(int capacity) { + this(null, capacity); + } + + @Override + public FluidStack getFluid() { + return fluid.copy(); + } + + @Override + public int getCapacity() { + return capacity; + } + + public boolean canAccept(FluidStack type) { + return type == null || (fluid.amount == 0 && changeType) || fluid.isFluidEqual(type); + } + + @Override + public int fill(FluidStack resource, boolean doFill) { + if (resource == null) return 0; + + if (!canAccept(resource)) return 0; + + int tofill = Math.min(getCapacity() - fluid.amount, resource.amount); + if (doFill && tofill > 0) { + if (!fluid.isFluidEqual(resource)) fluid = FluidUtils.copy(resource, fluid.amount + tofill); + else fluid.amount += tofill; + onLiquidChanged(); + } + + return tofill; + } + + @Override + public FluidStack drain(int maxDrain, boolean doDrain) { + if (fluid.amount == 0 || maxDrain <= 0) return null; + + int todrain = Math.min(maxDrain, fluid.amount); + if (doDrain && todrain > 0) { + fluid.amount -= todrain; + onLiquidChanged(); + } + return FluidUtils.copy(fluid, todrain); + } + + public FluidStack drain(FluidStack resource, boolean doDrain) { + if (resource == null || !resource.isFluidEqual(fluid)) return null; + + return drain(resource.amount, doDrain); + } + + public void onLiquidChanged() {} + + public void fromTag(NBTTagCompound tag) { + fluid = FluidUtils.read(tag); + } + + public NBTTagCompound toTag() { + return FluidUtils.write(fluid, new NBTTagCompound()); + } + + @Override + public int getFluidAmount() { + return fluid.amount; + } + + @Override + public FluidTankInfo getInfo() { + return new FluidTankInfo(this); + } +} diff --git a/src/codechicken/core/fluid/FluidUtils.java b/src/main/java/codechicken/core/fluid/FluidUtils.java similarity index 65% rename from src/codechicken/core/fluid/FluidUtils.java rename to src/main/java/codechicken/core/fluid/FluidUtils.java index 1466cc1..81e5ca3 100644 --- a/src/codechicken/core/fluid/FluidUtils.java +++ b/src/main/java/codechicken/core/fluid/FluidUtils.java @@ -1,6 +1,5 @@ package codechicken.core.fluid; -import codechicken.lib.inventory.InventoryUtils; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; @@ -11,87 +10,75 @@ import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidHandler; -public class FluidUtils -{ +import codechicken.lib.inventory.InventoryUtils; + +public class FluidUtils { + public static int B = FluidContainerRegistry.BUCKET_VOLUME; public static FluidStack water = new FluidStack(FluidRegistry.WATER, 1000); public static FluidStack lava = new FluidStack(FluidRegistry.LAVA, 1000); - public static boolean fillTankWithContainer(IFluidHandler tank, EntityPlayer player) - { + public static boolean fillTankWithContainer(IFluidHandler tank, EntityPlayer player) { ItemStack stack = player.getCurrentEquippedItem(); FluidStack liquid = FluidContainerRegistry.getFluidForFilledItem(stack); - if(liquid == null) - return false; + if (liquid == null) return false; - if(tank.fill(ForgeDirection.UNKNOWN, liquid, false) != liquid.amount && !player.capabilities.isCreativeMode) + if (tank.fill(ForgeDirection.UNKNOWN, liquid, false) != liquid.amount && !player.capabilities.isCreativeMode) return false; - + tank.fill(ForgeDirection.UNKNOWN, liquid, true); - - if(!player.capabilities.isCreativeMode) + + if (!player.capabilities.isCreativeMode) InventoryUtils.consumeItem(player.inventory, player.inventory.currentItem); player.inventoryContainer.detectAndSendChanges(); return true; } - public static boolean emptyTankIntoContainer(IFluidHandler tank, EntityPlayer player, FluidStack tankLiquid) - { + public static boolean emptyTankIntoContainer(IFluidHandler tank, EntityPlayer player, FluidStack tankLiquid) { ItemStack stack = player.getCurrentEquippedItem(); - if(!FluidContainerRegistry.isEmptyContainer(stack)) - return false; - + if (!FluidContainerRegistry.isEmptyContainer(stack)) return false; + ItemStack filled = FluidContainerRegistry.fillFluidContainer(tankLiquid, stack); FluidStack liquid = FluidContainerRegistry.getFluidForFilledItem(filled); - if(liquid == null || filled == null) - return false; - + if (liquid == null || filled == null) return false; + tank.drain(ForgeDirection.UNKNOWN, liquid.amount, true); - if(!player.capabilities.isCreativeMode) - { - if(stack.stackSize == 1) - player.inventory.setInventorySlotContents(player.inventory.currentItem, filled); - else if(player.inventory.addItemStackToInventory(filled)) - stack.stackSize--; - else - return false; + if (!player.capabilities.isCreativeMode) { + if (stack.stackSize == 1) player.inventory.setInventorySlotContents(player.inventory.currentItem, filled); + else if (player.inventory.addItemStackToInventory(filled)) stack.stackSize--; + else return false; } - - player.inventoryContainer.detectAndSendChanges(); + + player.inventoryContainer.detectAndSendChanges(); return true; } - public static FluidStack copy(FluidStack liquid, int quantity) - { + public static FluidStack copy(FluidStack liquid, int quantity) { liquid = liquid.copy(); liquid.amount = quantity; return liquid; } - public static FluidStack read(NBTTagCompound tag) - { + public static FluidStack read(NBTTagCompound tag) { FluidStack stack = FluidStack.loadFluidStackFromNBT(tag); return stack != null ? stack : FluidUtils.emptyFluid(); } - - public static NBTTagCompound write(FluidStack fluid, NBTTagCompound tag) - { - return fluid == null || fluid.getFluid() == null ? new NBTTagCompound() : fluid.writeToNBT(new NBTTagCompound()); + + public static NBTTagCompound write(FluidStack fluid, NBTTagCompound tag) { + return fluid == null || fluid.getFluid() == null ? new NBTTagCompound() + : fluid.writeToNBT(new NBTTagCompound()); } - public static int getLuminosity(FluidStack stack, double density) - { + public static int getLuminosity(FluidStack stack, double density) { Fluid fluid = stack.getFluid(); - if(fluid == null) - return 0; + if (fluid == null) return 0; int light = fluid.getLuminosity(stack); - if(fluid.isGaseous()) - light=(int)(light*density); + if (fluid.isGaseous()) light = (int) (light * density); return light; } diff --git a/src/codechicken/core/fluid/TankAccess.java b/src/main/java/codechicken/core/fluid/TankAccess.java similarity index 61% rename from src/codechicken/core/fluid/TankAccess.java rename to src/main/java/codechicken/core/fluid/TankAccess.java index 8cbdcf1..f1d2f52 100644 --- a/src/codechicken/core/fluid/TankAccess.java +++ b/src/main/java/codechicken/core/fluid/TankAccess.java @@ -4,29 +4,25 @@ import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidHandler; -public class TankAccess -{ +public class TankAccess { + public IFluidHandler tank; public ForgeDirection side; - - public TankAccess(IFluidHandler tank, ForgeDirection side) - { + + public TankAccess(IFluidHandler tank, ForgeDirection side) { this.tank = tank; this.side = side; } - - public TankAccess(IFluidHandler tank, int side) - { + + public TankAccess(IFluidHandler tank, int side) { this(tank, ForgeDirection.getOrientation(side)); } - public int fill(FluidStack resource, boolean doFill) - { + public int fill(FluidStack resource, boolean doFill) { return tank.fill(side, resource, doFill); } - - public FluidStack drain(int maxDrain, boolean doDrain) - { + + public FluidStack drain(int maxDrain, boolean doDrain) { return tank.drain(side, maxDrain, doDrain); } } diff --git a/src/codechicken/core/gui/ClickCounter.java b/src/main/java/codechicken/core/gui/ClickCounter.java similarity index 73% rename from src/codechicken/core/gui/ClickCounter.java rename to src/main/java/codechicken/core/gui/ClickCounter.java index 1e2181b..74f85cf 100644 --- a/src/codechicken/core/gui/ClickCounter.java +++ b/src/main/java/codechicken/core/gui/ClickCounter.java @@ -1,20 +1,20 @@ package codechicken.core.gui; -import com.google.common.base.Objects; - import java.util.Map; import java.util.TreeMap; -public class ClickCounter -{ - public class ClickCount - { +import com.google.common.base.Objects; + +public class ClickCounter { + + public class ClickCount { + public T clicked; public long time; public int count; public boolean update(T clicked) { - if(!Objects.equal(this.clicked, clicked)) { + if (!Objects.equal(this.clicked, clicked)) { this.clicked = clicked; count = 0; time = Long.MIN_VALUE; @@ -28,8 +28,7 @@ public boolean update(T clicked) { public ClickCount getCount(int button) { ClickCount c = buttons.get(button); - if(c == null) - buttons.put(button, c = new ClickCount()); + if (c == null) buttons.put(button, c = new ClickCount()); return c; } @@ -40,14 +39,11 @@ public void mouseDown(T clicked, int button) { public int mouseUp(T clicked, int button) { ClickCount c = getCount(button); - if(!c.update(clicked)) - return 0; + if (!c.update(clicked)) return 0; long time = System.currentTimeMillis(); - if(time-c.time < 500) - c.count++; - else - c.count = 1; + if (time - c.time < 500) c.count++; + else c.count = 1; c.time = time; return c.count; } diff --git a/src/codechicken/core/gui/GuiCCButton.java b/src/main/java/codechicken/core/gui/GuiCCButton.java similarity index 70% rename from src/codechicken/core/gui/GuiCCButton.java rename to src/main/java/codechicken/core/gui/GuiCCButton.java index c1571ef..8c81242 100644 --- a/src/codechicken/core/gui/GuiCCButton.java +++ b/src/main/java/codechicken/core/gui/GuiCCButton.java @@ -1,13 +1,13 @@ package codechicken.core.gui; import net.minecraft.client.Minecraft; - import net.minecraft.client.audio.PositionedSoundRecord; import net.minecraft.util.ResourceLocation; + import org.lwjgl.opengl.GL11; -public class GuiCCButton extends GuiWidget -{ +public class GuiCCButton extends GuiWidget { + public String text; public String actionCommand; private boolean isEnabled = true; @@ -34,28 +34,34 @@ public void setEnabled(boolean b) { public void mouseClicked(int x, int y, int button) { if (isEnabled && pointInside(x, y) && actionCommand != null) { sendAction(actionCommand, button); - Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("gui.button.press"), 1.0F)); + Minecraft.getMinecraft().getSoundHandler() + .playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("gui.button.press"), 1.0F)); } } @Override public void draw(int mousex, int mousey, float frame) { - if (!visible) - return; + if (!visible) return; drawButtonTex(mousex, mousey); - if(text != null) - drawText(mousex, mousey); + if (text != null) drawText(mousex, mousey); } public void drawButtonTex(int mousex, int mousey) { GL11.glColor4f(1, 1, 1, 1); renderEngine.bindTexture(guiTex); int state = getButtonTex(mousex, mousey); - drawTexturedModalRect(x, y, 0, 46 + state * 20, width / 2, height / 2);//top left - drawTexturedModalRect(x + width / 2, y, 200 - width / 2, 46 + state * 20, width / 2, height / 2);//top right - drawTexturedModalRect(x, y + height / 2, 0, 46 + state * 20 + 20 - height / 2, width / 2, height / 2);//bottom left - drawTexturedModalRect(x + width / 2, y + height / 2, 200 - width / 2, 46 + state * 20 + 20 - height / 2, width / 2, height / 2);//bottom right + drawTexturedModalRect(x, y, 0, 46 + state * 20, width / 2, height / 2); // top left + drawTexturedModalRect(x + width / 2, y, 200 - width / 2, 46 + state * 20, width / 2, height / 2); // top right + drawTexturedModalRect(x, y + height / 2, 0, 46 + state * 20 + 20 - height / 2, width / 2, height / 2); // bottom + // left + drawTexturedModalRect( + x + width / 2, + y + height / 2, + 200 - width / 2, + 46 + state * 20 + 20 - height / 2, + width / 2, + height / 2); // bottom right } public int getButtonTex(int mousex, int mousey) { diff --git a/src/main/java/codechicken/core/gui/GuiCCTextField.java b/src/main/java/codechicken/core/gui/GuiCCTextField.java new file mode 100644 index 0000000..015070f --- /dev/null +++ b/src/main/java/codechicken/core/gui/GuiCCTextField.java @@ -0,0 +1,150 @@ +package codechicken.core.gui; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.util.ChatAllowedCharacters; + +import org.lwjgl.input.Keyboard; + +public class GuiCCTextField extends GuiWidget { + + private String text; + private boolean isFocused; + private boolean isEnabled; + + public int maxStringLength; + public int cursorCounter; + public String actionCommand; + + private String allowedCharacters; + + public GuiCCTextField(int x, int y, int width, int height, String text) { + super(x, y, width, height); + isFocused = false; + isEnabled = true; + this.text = text; + } + + public GuiCCTextField setActionCommand(String s) { + actionCommand = s; + return this; + } + + public void setText(String s) { + if (s.equals(text)) return; + + String oldText = text; + text = s; + onTextChanged(oldText); + } + + public void onTextChanged(String oldText) {} + + public final String getText() { + return text; + } + + public final boolean isEnabled() { + return isEnabled; + } + + public void setEnabled(boolean b) { + isEnabled = b; + if (!isEnabled && isFocused) setFocused(false); + } + + public final boolean isFocused() { + return isFocused; + } + + @Override + public void update() { + cursorCounter++; + } + + @Override + public void keyTyped(char c, int keycode) { + if (!isEnabled || !isFocused) return; + + /* + * if(c == '\t')//tab { parentGuiScreen.selectNextField(); } + */ + if (c == '\026') // paste + { + String s = GuiScreen.getClipboardString(); + if (s == null || s.equals("")) return; + + for (int i = 0; i < s.length(); i++) { + if (text.length() == maxStringLength) return; + + char tc = s.charAt(i); + if (canAddChar(tc)) setText(text + tc); + } + } + if (keycode == Keyboard.KEY_RETURN) { + setFocused(false); + sendAction(actionCommand, getText()); + } + + if (keycode == Keyboard.KEY_BACK && text.length() > 0) setText(text.substring(0, text.length() - 1)); + + if ((text.length() < maxStringLength || maxStringLength == 0) && canAddChar(c)) setText(text + c); + } + + public boolean canAddChar(char c) { + return allowedCharacters == null ? ChatAllowedCharacters.isAllowedCharacter(c) + : allowedCharacters.indexOf(c) >= 0; + } + + @Override + public void mouseClicked(int x, int y, int button) { + if (isEnabled && pointInside(x, y)) { + setFocused(true); + if (button == 1) setText(""); + } else setFocused(false); + } + + public void setFocused(boolean focus) { + if (focus == isFocused) return; + isFocused = focus; + onFocusChanged(); + } + + public void onFocusChanged() { + if (isFocused) cursorCounter = 0; + } + + @Override + public void draw(int i, int j, float f) { + drawBackground(); + drawText(); + } + + public void drawBackground() { + drawRect(x - 1, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0); + drawRect(x, y, x + width, y + height, 0xff000000); + } + + public String getDrawText() { + String s = getText(); + if (isEnabled && isFocused && (cursorCounter / 6) % 2 == 0) s += "_"; + return s; + } + + public void drawText() { + drawString(fontRenderer, getDrawText(), x + 4, y + height / 2 - 4, getTextColour()); + } + + public int getTextColour() { + return isEnabled ? 0xe0e0e0 : 0x707070; + } + + public GuiCCTextField setMaxStringLength(int i) { + maxStringLength = i; + return this; + } + + public GuiCCTextField setAllowedCharacters(String s) { + allowedCharacters = s; + return this; + } +} diff --git a/src/codechicken/core/gui/GuiScreenWidget.java b/src/main/java/codechicken/core/gui/GuiScreenWidget.java similarity index 52% rename from src/codechicken/core/gui/GuiScreenWidget.java rename to src/main/java/codechicken/core/gui/GuiScreenWidget.java index 1138a9b..132692a 100644 --- a/src/codechicken/core/gui/GuiScreenWidget.java +++ b/src/main/java/codechicken/core/gui/GuiScreenWidget.java @@ -3,41 +3,37 @@ import java.awt.Point; import java.util.ArrayList; -import codechicken.lib.gui.GuiDraw; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; + import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiScreen; +import codechicken.lib.gui.GuiDraw; + +public class GuiScreenWidget extends GuiScreen implements IGuiActionListener { -public class GuiScreenWidget extends GuiScreen implements IGuiActionListener -{ public ArrayList widgets = new ArrayList(); public int xSize, ySize, guiTop, guiLeft; - public GuiScreenWidget() - { + public GuiScreenWidget() { this(176, 166); } - public GuiScreenWidget(int xSize, int ySize) - { + public GuiScreenWidget(int xSize, int ySize) { super(); this.xSize = xSize; this.ySize = ySize; } @Override - public void initGui() - { + public void initGui() { guiTop = (height - ySize) / 2; guiLeft = (width - xSize) / 2; - if(!widgets.isEmpty()) - resize(); + if (!widgets.isEmpty()) resize(); } - public void reset() - { + public void reset() { widgets.clear(); initGui(); addWidgets(); @@ -45,106 +41,78 @@ public void reset() } @Override - public void setWorldAndResolution(Minecraft mc, int i, int j) - { + public void setWorldAndResolution(Minecraft mc, int i, int j) { boolean init = this.mc == null; super.setWorldAndResolution(mc, i, j); - if(init) { + if (init) { addWidgets(); resize(); } } - public void add(GuiWidget widget) - { + public void add(GuiWidget widget) { widgets.add(widget); widget.onAdded(this); } @Override - public void drawScreen(int mousex, int mousey, float f) - { + public void drawScreen(int mousex, int mousey, float f) { GL11.glTranslated(guiLeft, guiTop, 0); drawBackground(); - for(GuiWidget widget : widgets) - widget.draw(mousex-guiLeft, mousey-guiTop, f); + for (GuiWidget widget : widgets) widget.draw(mousex - guiLeft, mousey - guiTop, f); drawForeground(); GL11.glTranslated(-guiLeft, -guiTop, 0); } - public void drawBackground() - { - } + public void drawBackground() {} - public void drawForeground() - { - } + public void drawForeground() {} @Override - protected void mouseClicked(int x, int y, int button) - { + protected void mouseClicked(int x, int y, int button) { super.mouseClicked(x, y, button); - for(GuiWidget widget : widgets) - widget.mouseClicked(x-guiLeft, y-guiTop, button); + for (GuiWidget widget : widgets) widget.mouseClicked(x - guiLeft, y - guiTop, button); } @Override - protected void mouseMovedOrUp(int x, int y, int button) - { + protected void mouseMovedOrUp(int x, int y, int button) { super.mouseMovedOrUp(x, y, button); - for(GuiWidget widget : widgets) - widget.mouseMovedOrUp(x-guiLeft, y-guiTop, button); + for (GuiWidget widget : widgets) widget.mouseMovedOrUp(x - guiLeft, y - guiTop, button); } @Override - protected void mouseClickMove(int x, int y, int button, long time) - { + protected void mouseClickMove(int x, int y, int button, long time) { super.mouseClickMove(x, y, button, time); - for(GuiWidget widget : widgets) - widget.mouseDragged(x-guiLeft, y-guiTop, button, time); + for (GuiWidget widget : widgets) widget.mouseDragged(x - guiLeft, y - guiTop, button, time); } @Override - public void updateScreen() - { + public void updateScreen() { super.updateScreen(); - if(mc.currentScreen == this) - for(GuiWidget widget : widgets) - widget.update(); + if (mc.currentScreen == this) for (GuiWidget widget : widgets) widget.update(); } @Override - public void keyTyped(char c, int keycode) - { + public void keyTyped(char c, int keycode) { super.keyTyped(c, keycode); - for(GuiWidget widget : widgets) - widget.keyTyped(c, keycode); + for (GuiWidget widget : widgets) widget.keyTyped(c, keycode); } @Override - public void handleMouseInput() - { + public void handleMouseInput() { super.handleMouseInput(); int i = Mouse.getEventDWheel(); - if(i != 0) - { + if (i != 0) { Point p = GuiDraw.getMousePosition(); int scroll = i > 0 ? 1 : -1; - for(GuiWidget widget : widgets) - widget.mouseScrolled(p.x, p.y, scroll); + for (GuiWidget widget : widgets) widget.mouseScrolled(p.x, p.y, scroll); } } @Override - public void actionPerformed(String ident, Object... params) - { - } + public void actionPerformed(String ident, Object... params) {} - public void resize() - { - } + public void resize() {} - public void addWidgets() - { - } + public void addWidgets() {} } diff --git a/src/codechicken/core/gui/GuiScrollPane.java b/src/main/java/codechicken/core/gui/GuiScrollPane.java similarity index 72% rename from src/codechicken/core/gui/GuiScrollPane.java rename to src/main/java/codechicken/core/gui/GuiScrollPane.java index e51050f..7b77c68 100644 --- a/src/codechicken/core/gui/GuiScrollPane.java +++ b/src/main/java/codechicken/core/gui/GuiScrollPane.java @@ -1,12 +1,13 @@ package codechicken.core.gui; +import java.awt.Dimension; +import java.awt.Rectangle; + import codechicken.lib.math.MathHelper; import codechicken.lib.vec.Rectangle4i; -import java.awt.*; +public abstract class GuiScrollPane extends GuiWidget { -public abstract class GuiScrollPane extends GuiWidget -{ protected int scrollclicky = -1; protected float scrollpercent; protected int scrollmousey; @@ -29,7 +30,11 @@ public void setMargins(int left, int top, int right, int bottom) { } public Rectangle windowBounds() { - return new Rectangle(x+marginleft, y+margintop, width-marginleft-marginright, height-margintop-marginbottom); + return new Rectangle( + x + marginleft, + y + margintop, + width - marginleft - marginright, + height - margintop - marginbottom); } public abstract int contentHeight(); @@ -42,8 +47,10 @@ public Dimension scrollbarDim() { public Rectangle scrollbarBounds() { Dimension dim = scrollbarDim(); return new Rectangle( - x + width - dim.width, y + (int) ((height - dim.height) * percentscrolled + 0.4999), - dim.width, dim.height); + x + width - dim.width, + y + (int) ((height - dim.height) * percentscrolled + 0.4999), + dim.width, + dim.height); } public void scrollUp() { @@ -55,7 +62,7 @@ public void scrollDown() { } public void scroll(int i) { - percentscrolled += i * height / (float)(2*contentHeight()); + percentscrolled += i * height / (float) (2 * contentHeight()); calculatePercentScrolled(); } @@ -102,15 +109,13 @@ public void mouseClicked(int mx, int my, int button) { Rectangle w = windowBounds(); int barempty = height - sbar.height; - if (button == 0 && - sbar.height < height && //the scroll bar can move (not full length) - mx >= sbar.x && mx <= sbar.x + sbar.width && - my >= y && my <= y + height)//in the scroll pane + if (button == 0 && sbar.height < height && // the scroll bar can move (not full length) + mx >= sbar.x && mx <= sbar.x + sbar.width && my >= y && my <= y + height) // in the scroll pane { if (my < sbar.y) { percentscrolled = (my - y) / (float) barempty; calculatePercentScrolled(); - } else if (my > sbar.y+sbar.height) { + } else if (my > sbar.y + sbar.height) { percentscrolled = (my - y - sbar.height + 1) / (float) barempty; calculatePercentScrolled(); } else { @@ -118,29 +123,25 @@ public void mouseClicked(int mx, int my, int button) { scrollpercent = percentscrolled; scrollmousey = my; } - } else if (w.contains(mx, my)) - slotDown(mx - w.x, my - w.y + scrolledPixels(), button); + } else if (w.contains(mx, my)) slotDown(mx - w.x, my - w.y + scrolledPixels(), button); } /** - * Mouse down on slot area - * Coordinates relative to slot content area + * Mouse down on slot area Coordinates relative to slot content area */ public void slotDown(int mx, int my, int button) {} /** - * Mouse up on slot area - * Coordinates relative to slot content area + * Mouse up on slot area Coordinates relative to slot content area */ public void slotUp(int mx, int my, int button) {} @Override public void mouseMovedOrUp(int mx, int my, int button) { Rectangle w = windowBounds(); - if (isScrolling() && button == 0)//we were scrolling and we released mouse + if (isScrolling() && button == 0) // we were scrolling and we released mouse scrollclicky = -1; - else if (w.contains(mx, my)) - slotUp(mx - w.x, my - w.y + scrolledPixels(), button); + else if (w.contains(mx, my)) slotUp(mx - w.x, my - w.y + scrolledPixels(), button); } @Override @@ -151,12 +152,9 @@ public void mouseDragged(int mousex, int mousey, int button, long time) { int barupallowed = (int) ((height - sbarh) * scrollpercent + 0.5); int bardownallowed = (height - sbarh) - barupallowed; - if (-scrolldiff > barupallowed) - scrollmousey = scrollclicky - barupallowed; - else if (scrolldiff > bardownallowed) - scrollmousey = scrollclicky + bardownallowed; - else - scrollmousey = mousey; + if (-scrolldiff > barupallowed) scrollmousey = scrollclicky - barupallowed; + else if (scrolldiff > bardownallowed) scrollmousey = scrollclicky + bardownallowed; + else scrollmousey = mousey; calculatePercentScrolled(); } @@ -180,7 +178,7 @@ public boolean contains(int mx, int my) { public void draw(int mx, int my, float frame) { Rectangle w = windowBounds(); drawBackground(frame); - drawContent(mx-w.x, my+scrolledPixels()-w.y, frame); + drawContent(mx - w.x, my + scrolledPixels() - w.y, frame); drawOverlay(frame); drawScrollbar(frame); } @@ -192,24 +190,22 @@ public void drawBackground(float frame) { public abstract void drawContent(int mx, int my, float frame); public void drawOverlay(float frame) { - //outlines - drawRect(x, y - 1, x + width, y, 0xffa0a0a0);//top - drawRect(x, y + height, x + width, y + height + 1, 0xffa0a0a0);//bottom - drawRect(x - 1, y - 1, x, y + height + 1, 0xffa0a0a0);//left - drawRect(x + width, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0);//right + // outlines + drawRect(x, y - 1, x + width, y, 0xffa0a0a0); // top + drawRect(x, y + height, x + width, y + height + 1, 0xffa0a0a0); // bottom + drawRect(x - 1, y - 1, x, y + height + 1, 0xffa0a0a0); // left + drawRect(x + width, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0); // right } public void drawScrollbar(float frame) { Rectangle r = scrollbarBounds(); - drawRect(r.x, r.y, r.x + r.width, r.y + r.height, 0xFF8B8B8B);//corners - drawRect(r.x, r.y, r.x + r.width - 1, r.y + r.height - 1, 0xFFF0F0F0);//topleft up - drawRect(r.x + 1, r.y + 1, r.x + r.width, r.y + r.height, 0xFF555555);//bottom right down - drawRect(r.x + 1, r.y + 1, r.x + r.width - 1, r.y + r.height - 1, 0xFFC6C6C6);//scrollbar + drawRect(r.x, r.y, r.x + r.width, r.y + r.height, 0xFF8B8B8B); // corners + drawRect(r.x, r.y, r.x + r.width - 1, r.y + r.height - 1, 0xFFF0F0F0); // topleft up + drawRect(r.x + 1, r.y + 1, r.x + r.width, r.y + r.height, 0xFF555555); // bottom right down + drawRect(r.x + 1, r.y + 1, r.x + r.width - 1, r.y + r.height - 1, 0xFFC6C6C6); // scrollbar int algn = scrollbarGuideAlignment(); - if (algn != 0) - drawRect(algn > 0 ? r.x + r.width : r.x - 1, y, r.x, y + height, 0xFF808080);//lineguide + if (algn != 0) drawRect(algn > 0 ? r.x + r.width : r.x - 1, y, r.x, y + height, 0xFF808080); // lineguide } - } diff --git a/src/codechicken/core/gui/GuiScrollSlot.java b/src/main/java/codechicken/core/gui/GuiScrollSlot.java similarity index 72% rename from src/codechicken/core/gui/GuiScrollSlot.java rename to src/main/java/codechicken/core/gui/GuiScrollSlot.java index e1da5dd..8e31626 100644 --- a/src/codechicken/core/gui/GuiScrollSlot.java +++ b/src/main/java/codechicken/core/gui/GuiScrollSlot.java @@ -1,11 +1,11 @@ package codechicken.core.gui; +import java.awt.Rectangle; + import org.lwjgl.input.Keyboard; -import java.awt.*; +public abstract class GuiScrollSlot extends GuiScrollPane { -public abstract class GuiScrollSlot extends GuiScrollPane -{ protected String actionCommand; public boolean focused; protected ClickCounter click = new ClickCounter(); @@ -40,13 +40,11 @@ public void selectPrev() {} protected abstract void drawSlot(int slot, int x, int y, int mx, int my, float frame); - protected void unfocus() { - } + protected void unfocus() {} public void setFocused(boolean focus) { focused = focus; - if (!focused) - unfocus(); + if (!focused) unfocus(); } @Override @@ -56,37 +54,34 @@ public int contentHeight() { public int getSlotY(int slot) { int h = 0; - for(int i = 0; i < slot; i++) - h+=getSlotHeight(i); + for (int i = 0; i < slot; i++) h += getSlotHeight(i); return h; } public int getSlot(int my) { - if (my < 0) - return -1; + if (my < 0) return -1; int y = 0; - for(int i = 0; i < getNumSlots(); i++) { + for (int i = 0; i < getNumSlots(); i++) { int h = getSlotHeight(i); - if(my >= y && my < y+h) - return i; - y+=h; + if (my >= y && my < y + h) return i; + y += h; } return -1; } public int getClickedSlot(int my) { - return getSlot(my-windowBounds().y+scrolledPixels()); + return getSlot(my - windowBounds().y + scrolledPixels()); } @Override public int scrolledPixels() { int scrolled = super.scrolledPixels(); - if(!smoothScroll) { + if (!smoothScroll) { int slot = getSlot(scrolled); int sloty = getSlotY(slot); int sloth = getSlotHeight(slot); - scrolled = sloty+(int)((scrolled-sloty)/(double)sloth+0.5)*sloth; + scrolled = sloty + (int) ((scrolled - sloty) / (double) sloth + 0.5) * sloth; } return scrolled; } @@ -106,8 +101,7 @@ public int scrollbarGuideAlignment() { @Override public Rectangle scrollbarBounds() { Rectangle r = super.scrollbarBounds(); - if(scrollbarAlignment() == -1) - r.x = x; + if (scrollbarAlignment() == -1) r.x = x; return r; } @@ -125,21 +119,16 @@ public void slotDown(int mx, int my, int button) { public void slotUp(int mx, int my, int button) { int slot = getSlot(my); int c = click.mouseUp(slot >= 0 ? slot : null, button); - if(c > 0 && slot >= 0) - slotClicked(slot, button, mx, my-getSlotY(slot), c); + if (c > 0 && slot >= 0) slotClicked(slot, button, mx, my - getSlotY(slot), c); } @Override public void keyTyped(char c, int keycode) { - if (!focused) - return; + if (!focused) return; - if (keycode == Keyboard.KEY_UP) - selectPrev(); - if (keycode == Keyboard.KEY_DOWN) - selectNext(); - if (keycode == Keyboard.KEY_RETURN && actionCommand != null) - sendAction(actionCommand); + if (keycode == Keyboard.KEY_UP) selectPrev(); + if (keycode == Keyboard.KEY_DOWN) selectNext(); + if (keycode == Keyboard.KEY_RETURN && actionCommand != null) sendAction(actionCommand); } @Override @@ -149,9 +138,9 @@ public void drawContent(int mx, int my, float frame) { int y = 0; for (int slot = 0; slot < getNumSlots(); slot++) { int h = getSlotHeight(slot); - if (y+h > scrolled && y < scrolled+w.height) - drawSlot(slot, w.x, w.y+y-scrolledPixels(), mx, my-y, frame); - y+=h; + if (y + h > scrolled && y < scrolled + w.height) + drawSlot(slot, w.x, w.y + y - scrolledPixels(), mx, my - y, frame); + y += h; } } } diff --git a/src/codechicken/core/gui/GuiWidget.java b/src/main/java/codechicken/core/gui/GuiWidget.java similarity index 62% rename from src/codechicken/core/gui/GuiWidget.java rename to src/main/java/codechicken/core/gui/GuiWidget.java index b73d1ee..e3a7bd9 100644 --- a/src/codechicken/core/gui/GuiWidget.java +++ b/src/main/java/codechicken/core/gui/GuiWidget.java @@ -7,78 +7,58 @@ import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.util.ResourceLocation; -public class GuiWidget extends Gui -{ +public class GuiWidget extends Gui { + protected static final ResourceLocation guiTex = new ResourceLocation("textures/gui/widgets.png"); - + public GuiScreen parentScreen; public TextureManager renderEngine; public FontRenderer fontRenderer; - + public int x; public int y; public int width; public int height; - - public GuiWidget(int x, int y, int width, int height) - { + + public GuiWidget(int x, int y, int width, int height) { setSize(x, y, width, height); } - - public void setSize(int x, int y, int width, int height) - { + + public void setSize(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; } - public boolean pointInside(int px, int py) - { + public boolean pointInside(int px, int py) { return px >= x && px < x + width && py >= y && py < y + height; } - - public void sendAction(String actionCommand, Object... params) - { + + public void sendAction(String actionCommand, Object... params) { sendAction(parentScreen, actionCommand, params); } - public static void sendAction(GuiScreen screen, String actionCommand, Object... params) - { - if(actionCommand != null && screen instanceof IGuiActionListener) + public static void sendAction(GuiScreen screen, String actionCommand, Object... params) { + if (actionCommand != null && screen instanceof IGuiActionListener) ((IGuiActionListener) screen).actionPerformed(actionCommand, params); } - public void mouseClicked(int x, int y, int button) - { - } + public void mouseClicked(int x, int y, int button) {} - public void mouseMovedOrUp(int x, int y, int button) - { - } - - public void mouseDragged(int x, int y, int button, long time) - { - } - - public void update() - { - } + public void mouseMovedOrUp(int x, int y, int button) {} - public void draw(int mousex, int mousey, float frame) - { - } + public void mouseDragged(int x, int y, int button, long time) {} - public void keyTyped(char c, int keycode) - { - } + public void update() {} - public void mouseScrolled(int x, int y, int scroll) - { - } - - public void onAdded(GuiScreen s) - { + public void draw(int mousex, int mousey, float frame) {} + + public void keyTyped(char c, int keycode) {} + + public void mouseScrolled(int x, int y, int scroll) {} + + public void onAdded(GuiScreen s) { Minecraft mc = Minecraft.getMinecraft(); parentScreen = s; renderEngine = mc.renderEngine; diff --git a/src/codechicken/core/gui/IGuiActionListener.java b/src/main/java/codechicken/core/gui/IGuiActionListener.java similarity index 71% rename from src/codechicken/core/gui/IGuiActionListener.java rename to src/main/java/codechicken/core/gui/IGuiActionListener.java index 8540251..4dfeb9e 100644 --- a/src/codechicken/core/gui/IGuiActionListener.java +++ b/src/main/java/codechicken/core/gui/IGuiActionListener.java @@ -1,6 +1,6 @@ package codechicken.core.gui; -public interface IGuiActionListener -{ +public interface IGuiActionListener { + public void actionPerformed(String actionCommand, Object... params); -} \ No newline at end of file +} diff --git a/src/codechicken/core/internal/CCCEventHandler.java b/src/main/java/codechicken/core/internal/CCCEventHandler.java similarity index 69% rename from src/codechicken/core/internal/CCCEventHandler.java rename to src/main/java/codechicken/core/internal/CCCEventHandler.java index 3775fe2..65f25b3 100644 --- a/src/codechicken/core/internal/CCCEventHandler.java +++ b/src/main/java/codechicken/core/internal/CCCEventHandler.java @@ -1,6 +1,6 @@ package codechicken.core.internal; -import codechicken.core.CCUpdateChecker; +import net.minecraftforge.client.event.GuiScreenEvent; import codechicken.core.GuiModListScroll; import cpw.mods.fml.client.GuiModList; @@ -9,31 +9,27 @@ import cpw.mods.fml.common.gameevent.TickEvent.Phase; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import net.minecraftforge.client.event.GuiScreenEvent; -public class CCCEventHandler -{ +public class CCCEventHandler { + public static int renderTime; public static float renderFrame; @SubscribeEvent public void clientTick(TickEvent.ClientTickEvent event) { - if(event.phase == Phase.END) { - CCUpdateChecker.tick(); + if (event.phase == Phase.END) { renderTime++; } } @SubscribeEvent public void renderTick(TickEvent.RenderTickEvent event) { - if(event.phase == Phase.START) - renderFrame = event.renderTickTime; + if (event.phase == Phase.START) renderFrame = event.renderTickTime; } @SubscribeEvent @SideOnly(Side.CLIENT) public void posGuiRender(GuiScreenEvent.DrawScreenEvent.Post event) { - if(event.gui instanceof GuiModList) - GuiModListScroll.draw((GuiModList)event.gui, event.mouseX, event.mouseY); + if (event.gui instanceof GuiModList) GuiModListScroll.draw((GuiModList) event.gui, event.mouseX, event.mouseY); } } diff --git a/src/codechicken/core/inventory/GuiContainerWidget.java b/src/main/java/codechicken/core/inventory/GuiContainerWidget.java similarity index 55% rename from src/codechicken/core/inventory/GuiContainerWidget.java rename to src/main/java/codechicken/core/inventory/GuiContainerWidget.java index 919ca8a..9255b28 100644 --- a/src/codechicken/core/inventory/GuiContainerWidget.java +++ b/src/main/java/codechicken/core/inventory/GuiContainerWidget.java @@ -3,125 +3,98 @@ import java.awt.Point; import java.util.ArrayList; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.inventory.Container; + import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; -import codechicken.lib.gui.GuiDraw; import codechicken.core.gui.GuiWidget; import codechicken.core.gui.IGuiActionListener; +import codechicken.lib.gui.GuiDraw; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.inventory.GuiContainer; -import net.minecraft.inventory.Container; +public class GuiContainerWidget extends GuiContainer implements IGuiActionListener { -public class GuiContainerWidget extends GuiContainer implements IGuiActionListener -{ public ArrayList widgets = new ArrayList(); - - public GuiContainerWidget(Container inventorySlots) - { + + public GuiContainerWidget(Container inventorySlots) { this(inventorySlots, 176, 166); } - - public GuiContainerWidget(Container inventorySlots, int xSize, int ySize) - { + + public GuiContainerWidget(Container inventorySlots, int xSize, int ySize) { super(inventorySlots); this.xSize = xSize; this.ySize = ySize; } - + @Override - public void setWorldAndResolution(Minecraft mc, int i, int j) - { + public void setWorldAndResolution(Minecraft mc, int i, int j) { super.setWorldAndResolution(mc, i, j); - if(widgets.isEmpty()) - addWidgets(); + if (widgets.isEmpty()) addWidgets(); } - - public void add(GuiWidget widget) - { + + public void add(GuiWidget widget) { widgets.add(widget); widget.onAdded(this); } @Override - protected void drawGuiContainerBackgroundLayer(float f, int mousex, int mousey) - { + protected void drawGuiContainerBackgroundLayer(float f, int mousex, int mousey) { GL11.glTranslated(guiLeft, guiTop, 0); drawBackground(); - for(GuiWidget widget : widgets) - widget.draw(mousex-guiLeft, mousey-guiTop, f); - + for (GuiWidget widget : widgets) widget.draw(mousex - guiLeft, mousey - guiTop, f); + GL11.glTranslated(-guiLeft, -guiTop, 0); } - - public void drawBackground() - { - } + + public void drawBackground() {} @Override - protected void mouseClicked(int x, int y, int button) - { + protected void mouseClicked(int x, int y, int button) { super.mouseClicked(x, y, button); - for(GuiWidget widget : widgets) - widget.mouseClicked(x-guiLeft, y-guiTop, button); + for (GuiWidget widget : widgets) widget.mouseClicked(x - guiLeft, y - guiTop, button); } - + @Override - protected void mouseMovedOrUp(int x, int y, int button) - { + protected void mouseMovedOrUp(int x, int y, int button) { super.mouseMovedOrUp(x, y, button); - for(GuiWidget widget : widgets) - widget.mouseMovedOrUp(x-guiLeft, y-guiTop, button); + for (GuiWidget widget : widgets) widget.mouseMovedOrUp(x - guiLeft, y - guiTop, button); } - + @Override - protected void mouseClickMove(int x, int y, int button, long time) - { + protected void mouseClickMove(int x, int y, int button, long time) { super.mouseClickMove(x, y, button, time); - for(GuiWidget widget : widgets) - widget.mouseDragged(x-guiLeft, y-guiTop, button, time); + for (GuiWidget widget : widgets) widget.mouseDragged(x - guiLeft, y - guiTop, button, time); } - + @Override - public void updateScreen() - { + public void updateScreen() { super.updateScreen(); - if(mc.currentScreen == this) - for(GuiWidget widget : widgets) - widget.update(); + if (mc.currentScreen == this) for (GuiWidget widget : widgets) widget.update(); } - + @Override - public void keyTyped(char c, int keycode) - { + public void keyTyped(char c, int keycode) { super.keyTyped(c, keycode); - for(GuiWidget widget : widgets) - widget.keyTyped(c, keycode); + for (GuiWidget widget : widgets) widget.keyTyped(c, keycode); } @Override - public void handleMouseInput() - { + public void handleMouseInput() { super.handleMouseInput(); int i = Mouse.getEventDWheel(); - if(i != 0) - { + if (i != 0) { Point p = GuiDraw.getMousePosition(); int scroll = i > 0 ? 1 : -1; - for(GuiWidget widget : widgets) - widget.mouseScrolled(p.x, p.y, scroll); + for (GuiWidget widget : widgets) widget.mouseScrolled(p.x, p.y, scroll); } } @Override - public void actionPerformed(String ident, Object... params) - { - } + public void actionPerformed(String ident, Object... params) {} - public void addWidgets() - { - } + public void addWidgets() {} @Override public void onGuiClosed() { diff --git a/src/main/java/codechicken/core/launch/CodeChickenCorePlugin.java b/src/main/java/codechicken/core/launch/CodeChickenCorePlugin.java new file mode 100644 index 0000000..a161722 --- /dev/null +++ b/src/main/java/codechicken/core/launch/CodeChickenCorePlugin.java @@ -0,0 +1,225 @@ +package codechicken.core.launch; + +import java.awt.Desktop; +import java.awt.GraphicsEnvironment; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; +import java.util.List; +import java.util.Map; + +import javax.swing.JEditorPane; +import javax.swing.JOptionPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import codechicken.core.asm.DelegatedTransformer; +import codechicken.core.asm.MCPDeobfuscationTransformer; +import codechicken.core.asm.Tags; +import codechicken.core.asm.TweakTransformer; +import codechicken.lib.config.ConfigFile; +import codechicken.lib.config.ConfigTag; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.versioning.DefaultArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; +import cpw.mods.fml.relauncher.CoreModManager; +import cpw.mods.fml.relauncher.FMLInjectionData; +import cpw.mods.fml.relauncher.IFMLLoadingPlugin; + +@IFMLLoadingPlugin.MCVersion("1.7.10") +@IFMLLoadingPlugin.TransformerExclusions({ "codechicken.core.asm", "codechicken.obfuscator", "codechicken.lib.asm", + "codechicken.lib.config" }) +public class CodeChickenCorePlugin implements IFMLLoadingPlugin { + + public static final String mcVersion = "[1.7.10]"; + + @Deprecated + public static final String version = Tags.VERSION; + + public static ConfigFile config; + public static File minecraftDir; + public static String currentMcVersion; + public static Logger logger = LogManager.getLogger("CodeChickenCore"); + + public CodeChickenCorePlugin() { + if (minecraftDir != null) return; + minecraftDir = (File) FMLInjectionData.data()[6]; + currentMcVersion = (String) FMLInjectionData.data()[4]; + config = new ConfigFile(new File(minecraftDir, "config/CodeChickenCore.cfg")) + .setComment("CodeChickenCore configuration file."); + if (Boolean.getBoolean("ccc.dev.deobfuscate")) { + injectDeobfPlugin(); + } + } + + @Override + public String[] getASMTransformerClass() { + versionCheck(mcVersion, "CodeChickenCore"); + return new String[] { "codechicken.lib.asm.ClassHeirachyManager", "codechicken.lib.asm.RedirectorTransformer" }; + } + + @Override + public String getAccessTransformerClass() { + if (Boolean.getBoolean("ccc.dev.runtimePublic")) { + return "codechicken.core.asm.CodeChickenAccessTransformer"; + } else { + return null; + } + } + + @Override + public String getModContainerClass() { + return "codechicken.core.asm.CodeChickenCoreModContainer"; + } + + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map data) { + ConfigTag checkRAM = CodeChickenCorePlugin.config.getTag("checks") + .setComment("Configuration options for checking various requirements for a modpack.").useBraces(); + if (checkRAM.getTag("checkRAM") + .setComment("If set to true, check RAM available for Minecraft before continuing to load") + .getBooleanValue(false)) { + systemCheck(checkRAM); + } + TweakTransformer.load(); + DelegatedTransformer.load(); + } + + private void injectDeobfPlugin() { + try { + Class wrapperClass = Class.forName("cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper"); + Constructor wrapperConstructor = wrapperClass + .getConstructor(String.class, IFMLLoadingPlugin.class, File.class, Integer.TYPE, String[].class); + Field f_loadPlugins = CoreModManager.class.getDeclaredField("loadPlugins"); + wrapperConstructor.setAccessible(true); + f_loadPlugins.setAccessible(true); + ((List) f_loadPlugins.get(null)).add( + 2, + wrapperConstructor.newInstance( + "CCCDeobfPlugin", + new MCPDeobfuscationTransformer.LoadPlugin(), + null, + 0, + new String[0])); + } catch (Exception e) { + logger.error("Failed to inject FMLPluginWrapper for MCPDeobfuscation Transformer", e); + } + } + + public static void versionCheck(String reqVersion, String mod) { + String mcVersion = (String) FMLInjectionData.data()[4]; + if (!VersionParser.parseRange(reqVersion).containsVersion(new DefaultArtifactVersion(mcVersion))) { + String err = "This version of " + mod + " does not support minecraft version " + mcVersion; + logger.error(err); + + JEditorPane ep = new JEditorPane( + "text/html", + "" + err + + "
Remove it from your coremods folder and check here for updates" + + ""); + + ep.setEditable(false); + ep.setOpaque(false); + ep.addHyperlinkListener(new HyperlinkListener() { + + @Override + public void hyperlinkUpdate(HyperlinkEvent event) { + try { + if (event.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) + Desktop.getDesktop().browse(event.getURL().toURI()); + } catch (Exception ignored) {} + } + }); + + JOptionPane.showMessageDialog(null, ep, "Fatal error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } + } + + public static long parseSize(String text) { + double d = Double.parseDouble(text.replaceAll("[GMK]B?$", "")); + long l = Math.round(d * 1024 * 1024 * 1024L); + switch (text.replaceAll("\\d?", "").toUpperCase().charAt(0)) { + default: + l /= 1024; + case 'K': + l /= 1024; + case 'M': + l /= 1024; + case 'G': + return l; + } + } + + public static String humanReadableByteCountBin(long bytes) { + long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes); + if (absB < 1024) { + return bytes + " B"; + } + long value = absB; + CharacterIterator ci = new StringCharacterIterator("KMGTPE"); + for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) { + value >>= 10; + ci.next(); + } + value *= Long.signum(bytes); + return String.format("%.1f %cB", value / 1024.0, ci.current()); + } + + public static void systemCheck(ConfigTag checkRAM) { + long minBytes = parseSize( + checkRAM.getTag("minRAM").setComment("Amount of RAM minimum this modpack needs to load") + .getValue("3GB")); + if (Runtime.getRuntime().maxMemory() < minBytes) { + String err = "You should have at least " + humanReadableByteCountBin(minBytes) + + " of RAM but you have only allocated " + + humanReadableByteCountBin(Runtime.getRuntime().maxMemory()) + + "."; + logger.error(err); + + JEditorPane ep = new JEditorPane( + "text/html", + "" + err + + "
" + + checkRAM.getTag("modPack").setComment("Name of the modpack") + .getValue("Unidentified ModPack") + + " seriously won't run without enough RAM. " + + checkRAM.getTag("wiki").setComment("Webpage describing RAM settings").getValue( + "See DownloadMoreRam.com for details.") + + "
Recommended values are between " + + checkRAM.getTag("recRAM").setComment("Lower bound of recommended RAM").getValue("4GB") + + " and " + + checkRAM.getTag("recRAMUpper").setComment("Upper bound of recommended RAM") + .getValue("6GB") + + ". Check your launcher's JVM arguments." + + ""); + + ep.setEditable(false); + ep.setOpaque(false); + ep.addHyperlinkListener(new HyperlinkListener() { + + @Override + public void hyperlinkUpdate(HyperlinkEvent event) { + try { + if (event.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) + Desktop.getDesktop().browse(event.getURL().toURI()); + } catch (Exception ignored) {} + } + }); + + if (!GraphicsEnvironment.isHeadless()) + JOptionPane.showMessageDialog(null, ep, "lol nope", JOptionPane.ERROR_MESSAGE); + FMLCommonHandler.instance().exitJava(-98, true); + } + } +} diff --git a/src/main/java/codechicken/lib/asm/ASMBlock.java b/src/main/java/codechicken/lib/asm/ASMBlock.java new file mode 100644 index 0000000..1f85ae7 --- /dev/null +++ b/src/main/java/codechicken/lib/asm/ASMBlock.java @@ -0,0 +1,177 @@ +package codechicken.lib.asm; + +import static org.objectweb.asm.tree.AbstractInsnNode.FRAME; +import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LABEL; +import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableMap; + +public class ASMBlock { + + public InsnListSection list; + private BiMap labels; + + public ASMBlock(InsnListSection list, BiMap labels) { + this.list = list; + this.labels = labels; + } + + public ASMBlock(InsnListSection list) { + this(list, HashBiMap.create()); + } + + public ASMBlock(InsnList list) { + this(new InsnListSection(list)); + } + + public ASMBlock() { + this(new InsnListSection()); + } + + public LabelNode getOrAdd(String s) { + LabelNode l = get(s); + if (l == null) labels.put(s, l = new LabelNode()); + return l; + } + + public LabelNode get(String s) { + return labels.get(s); + } + + public void replaceLabels(Map labelMap, Set usedLabels) { + for (AbstractInsnNode insn : list) switch (insn.getType()) { + case LABEL: + AbstractInsnNode insn2 = insn.clone(labelMap); + if (insn2 == insn) // identity mapping + continue; + if (usedLabels.contains(insn2)) + throw new IllegalStateException("LabelNode cannot be a part of two InsnLists"); + list.replace(insn, insn2); + break; + case JUMP_INSN: + case FRAME: + case LOOKUPSWITCH_INSN: + case TABLESWITCH_INSN: + list.replace(insn, insn.clone(labelMap)); + } + + for (Entry entry : labelMap.entrySet()) { + String key = labels.inverse().get(entry.getKey()); + if (key != null) labels.put(key, entry.getValue()); + } + } + + public void replaceLabels(Map labelMap) { + replaceLabels(labelMap, Collections.EMPTY_SET); + } + + public void replaceLabel(String s, LabelNode l) { + LabelNode old = get(s); + if (old != null) replaceLabels(ImmutableMap.of(old, l)); + } + + /** + * Pulls all common labels from other into this + * + * @return this + */ + public ASMBlock mergeLabels(ASMBlock other) { + if (labels.isEmpty() || other.labels.isEmpty()) return this; + + // common labels, give them our nodes + HashMap labelMap = list.identityLabelMap(); + for (Entry entry : other.labels.entrySet()) { + LabelNode old = labels.get(entry.getKey()); + if (old != null) labelMap.put(old, entry.getValue()); + } + HashSet usedLabels = new HashSet(); + for (AbstractInsnNode insn = other.list.list.getFirst(); insn != null; insn = insn.getNext()) + if (insn.getType() == LABEL) usedLabels.add((LabelNode) insn); + + replaceLabels(labelMap, usedLabels); + return this; + } + + /** + * Like mergeLabels but pulls insns from other list into this so LabelNodes can be transferred + * + * @return this + */ + public ASMBlock pullLabels(ASMBlock other) { + other.list.remove(); + return mergeLabels(other); + } + + public ASMBlock copy() { + BiMap labels = HashBiMap.create(); + Map labelMap = list.cloneLabels(); + + for (Entry entry : this.labels.entrySet()) + labels.put(entry.getKey(), labelMap.get(entry.getValue())); + + return new ASMBlock(list.copy(labelMap), labels); + } + + public ASMBlock applyLabels(InsnListSection list2) { + if (labels.isEmpty()) return new ASMBlock(list2); + + Set cFlowLabels1 = labels.values(); + Set cFlowLabels2 = InsnComparator.getControlFlowLabels(list2); + ASMBlock block = new ASMBlock(list2); + + HashMap labelMap = new HashMap(); + + for (int i = 0, k = 0; i < list.size() && k < list2.size();) { + AbstractInsnNode insn1 = list.get(i); + if (!InsnComparator.insnImportant(insn1, cFlowLabels1)) { + i++; + continue; + } + + AbstractInsnNode insn2 = list2.get(k); + if (!InsnComparator.insnImportant(insn2, cFlowLabels2)) { + k++; + continue; + } + + if (insn1.getOpcode() != insn2.getOpcode()) + throw new IllegalArgumentException("Lists do not match:\n" + list + "\n\n" + list2); + + switch (insn1.getType()) { + case LABEL: + labelMap.put((LabelNode) insn1, (LabelNode) insn2); + break; + case JUMP_INSN: + labelMap.put(((JumpInsnNode) insn1).label, ((JumpInsnNode) insn2).label); + break; + } + i++; + k++; + } + + for (Entry entry : labels.entrySet()) + block.labels.put(entry.getKey(), labelMap.get(entry.getValue())); + + return block; + } + + public InsnList rawListCopy() { + return list.copy().list; + } +} diff --git a/src/main/java/codechicken/lib/asm/ASMHelper.java b/src/main/java/codechicken/lib/asm/ASMHelper.java new file mode 100644 index 0000000..e90b99f --- /dev/null +++ b/src/main/java/codechicken/lib/asm/ASMHelper.java @@ -0,0 +1,203 @@ +package codechicken.lib.asm; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TryCatchBlockNode; +import org.objectweb.asm.util.ASMifier; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceClassVisitor; + +import codechicken.lib.config.ConfigFile; +import codechicken.lib.config.DefaultingConfigFile; + +public class ASMHelper { + + public static ConfigFile config = loadConfig(); + public static Logger logger = LogManager.getLogger("CCL ASM"); + + private static ConfigFile loadConfig() { + try { // weak reference for environments without FML + File mcDir = (File) ((Object[]) Class.forName("cpw.mods.fml.relauncher.FMLInjectionData").getMethod("data") + .invoke(null))[6]; + File file = new File(mcDir, "config/CodeChickenLib.cfg"); + if (ObfMapping.obfuscated) return new DefaultingConfigFile(file); + else return new ConfigFile(file).setComment("CodeChickenLib development configuration file."); + } catch (Exception ignored) { + return null; // no config for these systems + } + } + + public static interface Acceptor { + + public void accept(ClassVisitor cv) throws IOException; + } + + public static MethodNode findMethod(ObfMapping methodmap, ClassNode cnode) { + for (MethodNode mnode : cnode.methods) if (methodmap.matches(mnode)) return mnode; + return null; + } + + public static FieldNode findField(ObfMapping fieldmap, ClassNode cnode) { + for (FieldNode fnode : cnode.fields) if (fieldmap.matches(fnode)) return fnode; + return null; + } + + public static ClassNode createClassNode(byte[] bytes) { + return createClassNode(bytes, 0); + } + + public static ClassNode createClassNode(byte[] bytes, int flags) { + ClassNode cnode = new ClassNode(); + ClassReader reader = new ClassReader(bytes); + reader.accept(cnode, flags); + return cnode; + } + + public static byte[] createBytes(ClassNode cnode, int flags) { + ClassWriter cw = new CC_ClassWriter(flags); + cnode.accept(cw); + return cw.toByteArray(); + } + + public static Map cloneLabels(InsnList list) { + return new InsnListSection(list).cloneLabels(); + } + + public static InsnList cloneInsnList(InsnList list) { + return new InsnListSection(list).copy().list; + } + + public static InsnList cloneInsnList(Map labelMap, InsnList list) { + return new InsnListSection(list).copy(labelMap).list; + } + + public static List cloneTryCatchBlocks(Map labelMap, + List tcblocks) { + ArrayList clone = new ArrayList(); + for (TryCatchBlockNode node : tcblocks) clone.add( + new TryCatchBlockNode( + labelMap.get(node.start), + labelMap.get(node.end), + labelMap.get(node.handler), + node.type)); + + return clone; + } + + public static List cloneLocals(Map labelMap, + List locals) { + ArrayList clone = new ArrayList(locals.size()); + for (LocalVariableNode node : locals) clone.add( + new LocalVariableNode( + node.name, + node.desc, + node.signature, + labelMap.get(node.start), + labelMap.get(node.end), + node.index)); + + return clone; + } + + public static void copy(MethodNode src, MethodNode dst) { + Map labelMap = cloneLabels(src.instructions); + dst.instructions = cloneInsnList(labelMap, src.instructions); + dst.tryCatchBlocks = cloneTryCatchBlocks(labelMap, src.tryCatchBlocks); + if (src.localVariables != null) dst.localVariables = cloneLocals(labelMap, src.localVariables); + dst.visibleAnnotations = src.visibleAnnotations; + dst.invisibleAnnotations = src.invisibleAnnotations; + dst.visitMaxs(src.maxStack, src.maxLocals); + } + + public static String toString(InsnList list) { + return new InsnListSection(list).toString(); + } + + public static int getLocal(List list, String name) { + int found = -1; + for (LocalVariableNode node : list) { + if (node.name.equals(name)) { + if (found >= 0) throw new RuntimeException( + "Duplicate local variable: " + name + " not coded to handle this scenario."); + + found = node.index; + } + } + return found; + } + + public static void replaceMethod(MethodNode original, MethodNode replacement) { + original.instructions.clear(); + if (original.localVariables != null) original.localVariables.clear(); + if (original.tryCatchBlocks != null) original.tryCatchBlocks.clear(); + replacement.accept(original); + } + + public static void dump(Acceptor acceptor, File file, boolean filterImportant, boolean sortLocals, + boolean textify) { + try { + if (!file.getParentFile().exists()) file.getParentFile().mkdirs(); + if (!file.exists()) file.createNewFile(); + + PrintWriter pout = new PrintWriter(file); + ClassVisitor cv = new TraceClassVisitor(null, textify ? new Textifier() : new ASMifier(), pout); + if (filterImportant) cv = new ImportantInsnVisitor(cv); + if (sortLocals) cv = new LocalVariablesSorterVisitor(cv); + acceptor.accept(cv); + pout.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void dump(Acceptor acceptor, File file, boolean filterImportant, boolean sortLocals) { + dump(acceptor, file, filterImportant, sortLocals, config.getTag("textify").getBooleanValue(true)); + } + + public static void dump(final byte[] bytes, File file, boolean filterImportant, boolean sortLocals) { + dump(new Acceptor() { + + @Override + public void accept(ClassVisitor cv) { + new ClassReader(bytes).accept(cv, ClassReader.EXPAND_FRAMES); + } + }, file, filterImportant, sortLocals); + } + + public static void dump(final InputStream is, File file, boolean filterImportant, boolean sortLocals) { + dump(new Acceptor() { + + @Override + public void accept(ClassVisitor cv) throws IOException { + new ClassReader(is).accept(cv, ClassReader.EXPAND_FRAMES); + } + }, file, filterImportant, sortLocals); + } + + public static void dump(final ClassNode cnode, File file, boolean filterImportant, boolean sortLocals) { + dump(new Acceptor() { + + @Override + public void accept(ClassVisitor cv) { + cnode.accept(cv); + } + }, file, filterImportant, sortLocals); + } +} diff --git a/src/main/java/codechicken/lib/asm/ASMInit.java b/src/main/java/codechicken/lib/asm/ASMInit.java new file mode 100644 index 0000000..baa8a13 --- /dev/null +++ b/src/main/java/codechicken/lib/asm/ASMInit.java @@ -0,0 +1,19 @@ +package codechicken.lib.asm; + +import net.minecraft.launchwrapper.Launch; + +/** + * Initialisation class for using this package. Call this on coremod load + */ +public class ASMInit { + + private static boolean initialised = false; + + public static void init() { + if (!initialised) { + Launch.classLoader.addTransformerExclusion("codechicken.lib.asm"); + Launch.classLoader.addTransformerExclusion("codechicken.lib.config"); + initialised = true; + } + } +} diff --git a/src/main/java/codechicken/lib/asm/ASMReader.java b/src/main/java/codechicken/lib/asm/ASMReader.java new file mode 100644 index 0000000..b348649 --- /dev/null +++ b/src/main/java/codechicken/lib/asm/ASMReader.java @@ -0,0 +1,469 @@ +package codechicken.lib.asm; + +import static org.objectweb.asm.Opcodes.AALOAD; +import static org.objectweb.asm.Opcodes.AASTORE; +import static org.objectweb.asm.Opcodes.ACONST_NULL; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ANEWARRAY; +import static org.objectweb.asm.Opcodes.ARETURN; +import static org.objectweb.asm.Opcodes.ARRAYLENGTH; +import static org.objectweb.asm.Opcodes.ASTORE; +import static org.objectweb.asm.Opcodes.ATHROW; +import static org.objectweb.asm.Opcodes.BALOAD; +import static org.objectweb.asm.Opcodes.BASTORE; +import static org.objectweb.asm.Opcodes.BIPUSH; +import static org.objectweb.asm.Opcodes.CALOAD; +import static org.objectweb.asm.Opcodes.CASTORE; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.D2F; +import static org.objectweb.asm.Opcodes.D2I; +import static org.objectweb.asm.Opcodes.D2L; +import static org.objectweb.asm.Opcodes.DADD; +import static org.objectweb.asm.Opcodes.DALOAD; +import static org.objectweb.asm.Opcodes.DASTORE; +import static org.objectweb.asm.Opcodes.DCMPG; +import static org.objectweb.asm.Opcodes.DCMPL; +import static org.objectweb.asm.Opcodes.DCONST_0; +import static org.objectweb.asm.Opcodes.DCONST_1; +import static org.objectweb.asm.Opcodes.DDIV; +import static org.objectweb.asm.Opcodes.DLOAD; +import static org.objectweb.asm.Opcodes.DMUL; +import static org.objectweb.asm.Opcodes.DNEG; +import static org.objectweb.asm.Opcodes.DREM; +import static org.objectweb.asm.Opcodes.DRETURN; +import static org.objectweb.asm.Opcodes.DSTORE; +import static org.objectweb.asm.Opcodes.DSUB; +import static org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.DUP2; +import static org.objectweb.asm.Opcodes.DUP2_X1; +import static org.objectweb.asm.Opcodes.DUP2_X2; +import static org.objectweb.asm.Opcodes.DUP_X1; +import static org.objectweb.asm.Opcodes.DUP_X2; +import static org.objectweb.asm.Opcodes.F2D; +import static org.objectweb.asm.Opcodes.F2I; +import static org.objectweb.asm.Opcodes.F2L; +import static org.objectweb.asm.Opcodes.FADD; +import static org.objectweb.asm.Opcodes.FALOAD; +import static org.objectweb.asm.Opcodes.FASTORE; +import static org.objectweb.asm.Opcodes.FCMPG; +import static org.objectweb.asm.Opcodes.FCMPL; +import static org.objectweb.asm.Opcodes.FCONST_0; +import static org.objectweb.asm.Opcodes.FCONST_1; +import static org.objectweb.asm.Opcodes.FCONST_2; +import static org.objectweb.asm.Opcodes.FDIV; +import static org.objectweb.asm.Opcodes.FLOAD; +import static org.objectweb.asm.Opcodes.FMUL; +import static org.objectweb.asm.Opcodes.FNEG; +import static org.objectweb.asm.Opcodes.FREM; +import static org.objectweb.asm.Opcodes.FRETURN; +import static org.objectweb.asm.Opcodes.FSTORE; +import static org.objectweb.asm.Opcodes.FSUB; +import static org.objectweb.asm.Opcodes.GETFIELD; +import static org.objectweb.asm.Opcodes.GETSTATIC; +import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.I2B; +import static org.objectweb.asm.Opcodes.I2C; +import static org.objectweb.asm.Opcodes.I2D; +import static org.objectweb.asm.Opcodes.I2F; +import static org.objectweb.asm.Opcodes.I2L; +import static org.objectweb.asm.Opcodes.I2S; +import static org.objectweb.asm.Opcodes.IADD; +import static org.objectweb.asm.Opcodes.IALOAD; +import static org.objectweb.asm.Opcodes.IAND; +import static org.objectweb.asm.Opcodes.IASTORE; +import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.ICONST_1; +import static org.objectweb.asm.Opcodes.ICONST_2; +import static org.objectweb.asm.Opcodes.ICONST_3; +import static org.objectweb.asm.Opcodes.ICONST_4; +import static org.objectweb.asm.Opcodes.ICONST_5; +import static org.objectweb.asm.Opcodes.ICONST_M1; +import static org.objectweb.asm.Opcodes.IDIV; +import static org.objectweb.asm.Opcodes.IFEQ; +import static org.objectweb.asm.Opcodes.IFGE; +import static org.objectweb.asm.Opcodes.IFGT; +import static org.objectweb.asm.Opcodes.IFLE; +import static org.objectweb.asm.Opcodes.IFLT; +import static org.objectweb.asm.Opcodes.IFNE; +import static org.objectweb.asm.Opcodes.IFNONNULL; +import static org.objectweb.asm.Opcodes.IFNULL; +import static org.objectweb.asm.Opcodes.IF_ACMPEQ; +import static org.objectweb.asm.Opcodes.IF_ACMPNE; +import static org.objectweb.asm.Opcodes.IF_ICMPEQ; +import static org.objectweb.asm.Opcodes.IF_ICMPGE; +import static org.objectweb.asm.Opcodes.IF_ICMPGT; +import static org.objectweb.asm.Opcodes.IF_ICMPLE; +import static org.objectweb.asm.Opcodes.IF_ICMPLT; +import static org.objectweb.asm.Opcodes.IF_ICMPNE; +import static org.objectweb.asm.Opcodes.IINC; +import static org.objectweb.asm.Opcodes.ILOAD; +import static org.objectweb.asm.Opcodes.IMUL; +import static org.objectweb.asm.Opcodes.INEG; +import static org.objectweb.asm.Opcodes.INSTANCEOF; +import static org.objectweb.asm.Opcodes.INVOKEDYNAMIC; +import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Opcodes.IOR; +import static org.objectweb.asm.Opcodes.IREM; +import static org.objectweb.asm.Opcodes.IRETURN; +import static org.objectweb.asm.Opcodes.ISHL; +import static org.objectweb.asm.Opcodes.ISHR; +import static org.objectweb.asm.Opcodes.ISTORE; +import static org.objectweb.asm.Opcodes.ISUB; +import static org.objectweb.asm.Opcodes.IUSHR; +import static org.objectweb.asm.Opcodes.IXOR; +import static org.objectweb.asm.Opcodes.JSR; +import static org.objectweb.asm.Opcodes.L2D; +import static org.objectweb.asm.Opcodes.L2F; +import static org.objectweb.asm.Opcodes.L2I; +import static org.objectweb.asm.Opcodes.LADD; +import static org.objectweb.asm.Opcodes.LALOAD; +import static org.objectweb.asm.Opcodes.LAND; +import static org.objectweb.asm.Opcodes.LASTORE; +import static org.objectweb.asm.Opcodes.LCMP; +import static org.objectweb.asm.Opcodes.LCONST_0; +import static org.objectweb.asm.Opcodes.LCONST_1; +import static org.objectweb.asm.Opcodes.LDC; +import static org.objectweb.asm.Opcodes.LDIV; +import static org.objectweb.asm.Opcodes.LLOAD; +import static org.objectweb.asm.Opcodes.LMUL; +import static org.objectweb.asm.Opcodes.LNEG; +import static org.objectweb.asm.Opcodes.LOOKUPSWITCH; +import static org.objectweb.asm.Opcodes.LOR; +import static org.objectweb.asm.Opcodes.LREM; +import static org.objectweb.asm.Opcodes.LRETURN; +import static org.objectweb.asm.Opcodes.LSHL; +import static org.objectweb.asm.Opcodes.LSHR; +import static org.objectweb.asm.Opcodes.LSTORE; +import static org.objectweb.asm.Opcodes.LSUB; +import static org.objectweb.asm.Opcodes.LUSHR; +import static org.objectweb.asm.Opcodes.LXOR; +import static org.objectweb.asm.Opcodes.MONITORENTER; +import static org.objectweb.asm.Opcodes.MONITOREXIT; +import static org.objectweb.asm.Opcodes.MULTIANEWARRAY; +import static org.objectweb.asm.Opcodes.NEW; +import static org.objectweb.asm.Opcodes.NEWARRAY; +import static org.objectweb.asm.Opcodes.NOP; +import static org.objectweb.asm.Opcodes.POP; +import static org.objectweb.asm.Opcodes.POP2; +import static org.objectweb.asm.Opcodes.PUTFIELD; +import static org.objectweb.asm.Opcodes.PUTSTATIC; +import static org.objectweb.asm.Opcodes.RET; +import static org.objectweb.asm.Opcodes.RETURN; +import static org.objectweb.asm.Opcodes.SALOAD; +import static org.objectweb.asm.Opcodes.SASTORE; +import static org.objectweb.asm.Opcodes.SIPUSH; +import static org.objectweb.asm.Opcodes.SWAP; +import static org.objectweb.asm.Opcodes.TABLESWITCH; +import static org.objectweb.asm.tree.AbstractInsnNode.FIELD_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.FRAME; +import static org.objectweb.asm.tree.AbstractInsnNode.IINC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.INT_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.INVOKE_DYNAMIC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LABEL; +import static org.objectweb.asm.tree.AbstractInsnNode.LDC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.MULTIANEWARRAY_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TYPE_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.VAR_INSN; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.MultiANewArrayInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +public class ASMReader { + + public static Map opCodes = new HashMap(); + public static byte[] TYPE; + + static { + opCodes.put("NOP", NOP); + opCodes.put("ACONST_NULL", ACONST_NULL); + opCodes.put("ICONST_M1", ICONST_M1); + opCodes.put("ICONST_0", ICONST_0); + opCodes.put("ICONST_1", ICONST_1); + opCodes.put("ICONST_2", ICONST_2); + opCodes.put("ICONST_3", ICONST_3); + opCodes.put("ICONST_4", ICONST_4); + opCodes.put("ICONST_5", ICONST_5); + opCodes.put("LCONST_0", LCONST_0); + opCodes.put("LCONST_1", LCONST_1); + opCodes.put("FCONST_0", FCONST_0); + opCodes.put("FCONST_1", FCONST_1); + opCodes.put("FCONST_2", FCONST_2); + opCodes.put("DCONST_0", DCONST_0); + opCodes.put("DCONST_1", DCONST_1); + opCodes.put("BIPUSH", BIPUSH); + opCodes.put("SIPUSH", SIPUSH); + opCodes.put("LDC", LDC); + opCodes.put("ILOAD", ILOAD); + opCodes.put("LLOAD", LLOAD); + opCodes.put("FLOAD", FLOAD); + opCodes.put("DLOAD", DLOAD); + opCodes.put("ALOAD", ALOAD); + opCodes.put("IALOAD", IALOAD); + opCodes.put("LALOAD", LALOAD); + opCodes.put("FALOAD", FALOAD); + opCodes.put("DALOAD", DALOAD); + opCodes.put("AALOAD", AALOAD); + opCodes.put("BALOAD", BALOAD); + opCodes.put("CALOAD", CALOAD); + opCodes.put("SALOAD", SALOAD); + opCodes.put("ISTORE", ISTORE); + opCodes.put("LSTORE", LSTORE); + opCodes.put("FSTORE", FSTORE); + opCodes.put("DSTORE", DSTORE); + opCodes.put("ASTORE", ASTORE); + opCodes.put("IASTORE", IASTORE); + opCodes.put("LASTORE", LASTORE); + opCodes.put("FASTORE", FASTORE); + opCodes.put("DASTORE", DASTORE); + opCodes.put("AASTORE", AASTORE); + opCodes.put("BASTORE", BASTORE); + opCodes.put("CASTORE", CASTORE); + opCodes.put("SASTORE", SASTORE); + opCodes.put("POP", POP); + opCodes.put("POP2", POP2); + opCodes.put("DUP", DUP); + opCodes.put("DUP_X1", DUP_X1); + opCodes.put("DUP_X2", DUP_X2); + opCodes.put("DUP2", DUP2); + opCodes.put("DUP2_X1", DUP2_X1); + opCodes.put("DUP2_X2", DUP2_X2); + opCodes.put("SWAP", SWAP); + opCodes.put("IADD", IADD); + opCodes.put("LADD", LADD); + opCodes.put("FADD", FADD); + opCodes.put("DADD", DADD); + opCodes.put("ISUB", ISUB); + opCodes.put("LSUB", LSUB); + opCodes.put("FSUB", FSUB); + opCodes.put("DSUB", DSUB); + opCodes.put("IMUL", IMUL); + opCodes.put("LMUL", LMUL); + opCodes.put("FMUL", FMUL); + opCodes.put("DMUL", DMUL); + opCodes.put("IDIV", IDIV); + opCodes.put("LDIV", LDIV); + opCodes.put("FDIV", FDIV); + opCodes.put("DDIV", DDIV); + opCodes.put("IREM", IREM); + opCodes.put("LREM", LREM); + opCodes.put("FREM", FREM); + opCodes.put("DREM", DREM); + opCodes.put("INEG", INEG); + opCodes.put("LNEG", LNEG); + opCodes.put("FNEG", FNEG); + opCodes.put("DNEG", DNEG); + opCodes.put("ISHL", ISHL); + opCodes.put("LSHL", LSHL); + opCodes.put("ISHR", ISHR); + opCodes.put("LSHR", LSHR); + opCodes.put("IUSHR", IUSHR); + opCodes.put("LUSHR", LUSHR); + opCodes.put("IAND", IAND); + opCodes.put("LAND", LAND); + opCodes.put("IOR", IOR); + opCodes.put("LOR", LOR); + opCodes.put("IXOR", IXOR); + opCodes.put("LXOR", LXOR); + opCodes.put("IINC", IINC); + opCodes.put("I2L", I2L); + opCodes.put("I2F", I2F); + opCodes.put("I2D", I2D); + opCodes.put("L2I", L2I); + opCodes.put("L2F", L2F); + opCodes.put("L2D", L2D); + opCodes.put("F2I", F2I); + opCodes.put("F2L", F2L); + opCodes.put("F2D", F2D); + opCodes.put("D2I", D2I); + opCodes.put("D2L", D2L); + opCodes.put("D2F", D2F); + opCodes.put("I2B", I2B); + opCodes.put("I2C", I2C); + opCodes.put("I2S", I2S); + opCodes.put("LCMP", LCMP); + opCodes.put("FCMPL", FCMPL); + opCodes.put("FCMPG", FCMPG); + opCodes.put("DCMPL", DCMPL); + opCodes.put("DCMPG", DCMPG); + opCodes.put("IFEQ", IFEQ); + opCodes.put("IFNE", IFNE); + opCodes.put("IFLT", IFLT); + opCodes.put("IFGE", IFGE); + opCodes.put("IFGT", IFGT); + opCodes.put("IFLE", IFLE); + opCodes.put("IF_ICMPEQ", IF_ICMPEQ); + opCodes.put("IF_ICMPNE", IF_ICMPNE); + opCodes.put("IF_ICMPLT", IF_ICMPLT); + opCodes.put("IF_ICMPGE", IF_ICMPGE); + opCodes.put("IF_ICMPGT", IF_ICMPGT); + opCodes.put("IF_ICMPLE", IF_ICMPLE); + opCodes.put("IF_ACMPEQ", IF_ACMPEQ); + opCodes.put("IF_ACMPNE", IF_ACMPNE); + opCodes.put("GOTO", GOTO); + opCodes.put("JSR", JSR); + opCodes.put("RET", RET); + opCodes.put("TABLESWITCH", TABLESWITCH); + opCodes.put("LOOKUPSWITCH", LOOKUPSWITCH); + opCodes.put("IRETURN", IRETURN); + opCodes.put("LRETURN", LRETURN); + opCodes.put("FRETURN", FRETURN); + opCodes.put("DRETURN", DRETURN); + opCodes.put("ARETURN", ARETURN); + opCodes.put("RETURN", RETURN); + opCodes.put("GETSTATIC", GETSTATIC); + opCodes.put("PUTSTATIC", PUTSTATIC); + opCodes.put("GETFIELD", GETFIELD); + opCodes.put("PUTFIELD", PUTFIELD); + opCodes.put("INVOKEVIRTUAL", INVOKEVIRTUAL); + opCodes.put("INVOKESPECIAL", INVOKESPECIAL); + opCodes.put("INVOKESTATIC", INVOKESTATIC); + opCodes.put("INVOKEINTERFACE", INVOKEINTERFACE); + opCodes.put("INVOKEDYNAMIC", INVOKEDYNAMIC); + opCodes.put("NEW", NEW); + opCodes.put("NEWARRAY", NEWARRAY); + opCodes.put("ANEWARRAY", ANEWARRAY); + opCodes.put("ARRAYLENGTH", ARRAYLENGTH); + opCodes.put("ATHROW", ATHROW); + opCodes.put("CHECKCAST", CHECKCAST); + opCodes.put("INSTANCEOF", INSTANCEOF); + opCodes.put("MONITORENTER", MONITORENTER); + opCodes.put("MONITOREXIT", MONITOREXIT); + opCodes.put("MULTIANEWARRAY", MULTIANEWARRAY); + opCodes.put("IFNULL", IFNULL); + opCodes.put("IFNONNULL", IFNONNULL); + + // derived from classWriter, mapped to AbstractInsnNode + TYPE = new byte[200]; + String s = "AAAAAAAAAAAAAAAABBJ__CCCCC____________________AAAAAAAACC" + + "CCC____________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHCLMAA" + + "AAAAEEEEFFFFGDBDAADDAA_NHH"; + for (int i = 0; i < s.length(); i++) TYPE[i] = (byte) (s.charAt(i) - 'A'); + } + + public static Map loadResource(String res) { + return loadResource(ASMHelper.class.getResourceAsStream(res), res); + } + + public static Map loadResource(InputStream in, String res) { + HashMap blocks = new HashMap(); + String current = "unnamed"; + ASMBlock block = new ASMBlock(); + try { + BufferedReader r = new BufferedReader(new InputStreamReader(in)); + String line; + while ((line = r.readLine()) != null) { + { + int hpos = line.indexOf('#'); + if (hpos >= 0) line = line.substring(0, hpos); + } + line = line.trim(); + if (line.length() == 0) continue; + if (line.startsWith("list ")) { + if (block.list.size() > 0) blocks.put(current, block); + current = line.substring(5); + block = new ASMBlock(); + continue; + } + + try { + AbstractInsnNode insn = null; + String[] split = line.replace(" : ", ":").split(" "); + Integer i_opcode = opCodes.get(split[0]); + if (i_opcode == null) { + if (split[0].equals("LINENUMBER")) + insn = new LineNumberNode(Integer.parseInt(split[1]), block.getOrAdd(split[2])); + else if (split[0].startsWith("L")) insn = block.getOrAdd(split[0]); + else throw new Exception("Unknown opcode " + split[0]); + } else { + int opcode = i_opcode; + switch (TYPE[opcode]) { + case INSN: + insn = new InsnNode(opcode); + break; + case INT_INSN: + insn = new IntInsnNode(opcode, Integer.parseInt(split[1])); + break; + case VAR_INSN: + insn = new VarInsnNode(opcode, Integer.parseInt(split[1])); + break; + case TYPE_INSN: + insn = new ObfMapping(split[1]).toClassloading().toInsn(opcode); + break; + case FIELD_INSN: + case METHOD_INSN: + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < split.length; i++) sb.append(split[i]); + insn = ObfMapping.fromDesc(sb.toString()).toClassloading().toInsn(opcode); + break; + case INVOKE_DYNAMIC_INSN: + throw new Exception("Found INVOKEDYNAMIC while reading"); + case JUMP_INSN: + insn = new JumpInsnNode(opcode, block.getOrAdd(split[1])); + break; + case LDC_INSN: + String cst = split[1]; + if (cst.equals("*")) insn = new LdcInsnNode(null); + else if (cst.endsWith("\"")) insn = new LdcInsnNode(cst.substring(1, cst.length() - 1)); + else if (cst.endsWith("L")) + insn = new LdcInsnNode(Long.valueOf(cst.substring(0, cst.length() - 1))); + else if (cst.endsWith("F")) + insn = new LdcInsnNode(Float.valueOf(cst.substring(0, cst.length() - 1))); + else if (cst.endsWith("D")) + insn = new LdcInsnNode(Double.valueOf(cst.substring(0, cst.length() - 1))); + else if (cst.contains(".")) insn = new LdcInsnNode(Double.valueOf(cst)); + else insn = new LdcInsnNode(Integer.valueOf(cst)); + break; + case IINC_INSN: + insn = new IincInsnNode(opcode, Integer.parseInt(split[1])); + break; + case LABEL: + throw new Exception("Use L# for labels"); + case TABLESWITCH_INSN: + case LOOKUPSWITCH_INSN: + throw new Exception("I don't know how to deal with this insn type"); + case MULTIANEWARRAY_INSN: + insn = new MultiANewArrayInsnNode(split[1], Integer.parseInt(split[2])); + break; + case FRAME: + throw new Exception("Use ClassWriter.COMPUTE_FRAMES"); + } + } + + if (insn != null) block.list.add(insn); + } catch (Exception e) { + System.err.println("Error while reading ASM Block " + current + " from " + res + ", line: " + line); + e.printStackTrace(); + } + } + + r.close(); + if (block.list.size() > 0) blocks.put(current, block); + } catch (IOException e) { + throw new RuntimeException("Failed to read ASM resource: " + res, e); + } + return blocks; + } +} diff --git a/src/main/java/codechicken/lib/asm/CC_ClassWriter.java b/src/main/java/codechicken/lib/asm/CC_ClassWriter.java new file mode 100644 index 0000000..f3e0731 --- /dev/null +++ b/src/main/java/codechicken/lib/asm/CC_ClassWriter.java @@ -0,0 +1,29 @@ +package codechicken.lib.asm; + +import org.objectweb.asm.ClassWriter; + +public class CC_ClassWriter extends ClassWriter { + + private final boolean runtime; + + public CC_ClassWriter(int flags) { + this(flags, false); + } + + public CC_ClassWriter(int flags, boolean runtime) { + super(flags); + this.runtime = runtime; + } + + @Override + protected String getCommonSuperClass(String type1, String type2) { + String c = type1.replace('/', '.'); + String d = type2.replace('/', '.'); + if (ClassHeirachyManager.classExtends(d, c)) return type1; + if (ClassHeirachyManager.classExtends(c, d)) return type2; + do { + c = ClassHeirachyManager.getSuperClass(c, runtime); + } while (!ClassHeirachyManager.classExtends(d, c)); + return c.replace('.', '/'); + } +} diff --git a/src/main/java/codechicken/lib/asm/ClassConstantPoolParser.java b/src/main/java/codechicken/lib/asm/ClassConstantPoolParser.java new file mode 100644 index 0000000..dffcfbe --- /dev/null +++ b/src/main/java/codechicken/lib/asm/ClassConstantPoolParser.java @@ -0,0 +1,109 @@ +package codechicken.lib.asm; + +/*** + * This Class is derived from the ASM ClassReader + *

+ * ASM: a very small and fast Java bytecode manipulation framework Copyright (c) 2000-2011 INRIA, France Telecom All + * rights reserved. + *

+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or other materials provided with the + * distribution. 3. Neither the name of the copyright holders nor the names of its contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import java.nio.charset.StandardCharsets; + +import org.objectweb.asm.Opcodes; + +/** + * Using this class to search for a (single) String reference is > 40 times faster than parsing a class with a + * ClassReader + ClassNode while using way less RAM + */ +public class ClassConstantPoolParser { + + private static final int UTF8 = 1; + private static final int INT = 3; + private static final int FLOAT = 4; + private static final int LONG = 5; + private static final int DOUBLE = 6; + private static final int FIELD = 9; + private static final int METH = 10; + private static final int IMETH = 11; + private static final int NAME_TYPE = 12; + private static final int HANDLE = 15; + private static final int INDY = 18; + + private final byte[][] BYTES_TO_SEARCH; + + public ClassConstantPoolParser(String... strings) { + BYTES_TO_SEARCH = new byte[strings.length][]; + for (int i = 0; i < BYTES_TO_SEARCH.length; i++) { + BYTES_TO_SEARCH[i] = strings[i].getBytes(StandardCharsets.UTF_8); + } + } + + /** + * Returns true if the constant pool of the class represented by this byte array contains one of the Strings we are + * looking for + */ + public boolean find(byte[] basicClass) { + if (basicClass == null || basicClass.length == 0) { + return false; + } + // checks the class version + if (readShort(6, basicClass) > Opcodes.V1_8) { + return false; + } + // parses the constant pool + int n = readUnsignedShort(8, basicClass); + int index = 10; + for (int i = 1; i < n; ++i) { + int size; + switch (basicClass[index]) { + case FIELD, METH, IMETH, INT, FLOAT, NAME_TYPE, INDY -> size = 5; + case LONG, DOUBLE -> { + size = 9; + ++i; + } + case UTF8 -> { + final int strLen = readUnsignedShort(index + 1, basicClass); + size = 3 + strLen; + label: for (byte[] bytes : BYTES_TO_SEARCH) { + if (strLen == bytes.length) { + for (int j = index + 3; j < index + 3 + strLen; j++) { + if (basicClass[j] != bytes[j - (index + 3)]) { + continue label; + } + } + return true; + } + } + } + case HANDLE -> size = 4; + default -> size = 3; + } + index += size; + } + return false; + } + + private static short readShort(final int index, byte[] basicClass) { + return (short) (((basicClass[index] & 0xFF) << 8) | (basicClass[index + 1] & 0xFF)); + } + + private static int readUnsignedShort(final int index, byte[] basicClass) { + return ((basicClass[index] & 0xFF) << 8) | (basicClass[index + 1] & 0xFF); + } + +} diff --git a/src/main/java/codechicken/lib/asm/ClassHeirachyManager.java b/src/main/java/codechicken/lib/asm/ClassHeirachyManager.java new file mode 100644 index 0000000..f14932d --- /dev/null +++ b/src/main/java/codechicken/lib/asm/ClassHeirachyManager.java @@ -0,0 +1,173 @@ +package codechicken.lib.asm; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.Launch; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; + +import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; + +/** + * This is added as a class transformer if CodeChickenCore is installed. Adding it as a class transformer will speed + * evaluation up slightly by automatically caching superclasses when they are first loaded. + */ +public class ClassHeirachyManager implements IClassTransformer { + + private static final Map superclasses = new ConcurrentHashMap<>(1000); + + static { + superclasses.put("java.lang.Object", ClassInfo.OBJECT); + } + + @Override + public byte[] transform(String name, String transformedName, byte[] bytes) { + if (bytes == null) return null; + if (!superclasses.containsKey(transformedName)) { + declareASM(transformedName, bytes); + } + return bytes; + } + + private static ClassInfo declareASM(String name, byte[] bytes) { + final ClassNode node = ASMHelper.createClassNode(bytes, ClassReader.SKIP_CODE); + final ClassInfo classInfo = new ClassInfo(node); + superclasses.put(name, classInfo); + return classInfo; + } + + private static ClassInfo declareReflection(String name) throws ClassNotFoundException { + final Class aclass = Class.forName(name); + final ClassInfo classInfo = new ClassInfo(aclass); + superclasses.put(name, classInfo); + return classInfo; + } + + private static ClassInfo declareClass(String name) { + try { + byte[] bytes = Launch.classLoader.getClassBytes(unKey(name)); + if (bytes != null) { + return declareASM(name, bytes); + } + } catch (Exception ignored) {} + try { + return declareReflection(name); + } catch (ClassNotFoundException ignored) {} + return null; + } + + private static String toKey(String name) { + if (ObfMapping.obfuscated) { + return FMLDeobfuscatingRemapper.INSTANCE.map(name.replace('.', '/')).replace('/', '.'); + } + return name.replace('/', '.'); + } + + private static String unKey(String name) { + if (ObfMapping.obfuscated) { + return FMLDeobfuscatingRemapper.INSTANCE.unmap(name.replace('.', '/')).replace('/', '.'); + } + return name.replace('/', '.'); + } + + /** + * @param name The class in question + * @param superclass The class being extended + * @return true if clazz extends, either directly or indirectly, superclass. + */ + public static boolean classExtends(String name, String superclass) { + name = toKey(name); + superclass = toKey(superclass); + + if (name.equals(superclass)) return true; + + ClassInfo classInfo = superclasses.get(name); + if (classInfo == null) classInfo = declareClass(name); + if (classInfo == null) return false; + + return classInfo.hasSuper(superclass); + } + + public static String getSuperClass(String name, boolean runtime) { + name = toKey(name); + + ClassInfo classInfo = superclasses.get(name); + if (classInfo == null) classInfo = declareClass(name); + if (classInfo == null || classInfo.superclass == null) return "java.lang.Object"; + + final String s = classInfo.superclass; + if (!runtime) { + return FMLDeobfuscatingRemapper.INSTANCE.unmap(s); + } + return s; + } + + private static class ClassInfo { + + public static final ClassInfo OBJECT = new ClassInfo(); + + public final String superclass; + public final String[] interfaces; + public ClassInfo parent; + + private ClassInfo() { + this.superclass = null; + this.interfaces = null; + } + + public ClassInfo(ClassNode node) { + if ("java/lang/Object".equals(node.superName)) { + superclass = "java.lang.Object"; + parent = OBJECT; + } else { + superclass = toKey(node.superName); + } + if (node.interfaces.isEmpty()) { + interfaces = null; + } else { + interfaces = new String[node.interfaces.size()]; + for (int i = 0; i < node.interfaces.size(); i++) { + interfaces[i] = toKey(node.interfaces.get(i)); + } + } + } + + public ClassInfo(Class aclass) { + if (aclass.isInterface()) { + this.superclass = "java.lang.Object"; + this.parent = OBJECT; + } else { + final Class superclass = aclass.getSuperclass(); + if (superclass == null || "java.lang.Object".equals(superclass.getName())) { + this.superclass = "java.lang.Object"; + this.parent = OBJECT; + } else { + this.superclass = toKey(superclass.getName()); + } + } + final Class[] interfaces = aclass.getInterfaces(); + if (interfaces.length == 0) { + this.interfaces = null; + } else { + this.interfaces = new String[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + this.interfaces[i] = toKey(interfaces[i].getName()); + } + } + } + + public boolean hasSuper(String superclass) { + if (this.superclass == null) return false; + if (this.superclass.equals(superclass)) return true; + ClassInfo parentInfo = this.parent; + if (parentInfo == null) parentInfo = superclasses.get(this.superclass); + if (parentInfo == null) parentInfo = declareClass(this.superclass); + if (parentInfo == null) return false; + this.parent = parentInfo; + return parentInfo.hasSuper(superclass); + } + } +} diff --git a/src/main/java/codechicken/lib/asm/ImportantInsnVisitor.java b/src/main/java/codechicken/lib/asm/ImportantInsnVisitor.java new file mode 100644 index 0000000..c6b32ed --- /dev/null +++ b/src/main/java/codechicken/lib/asm/ImportantInsnVisitor.java @@ -0,0 +1,36 @@ +package codechicken.lib.asm; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +public class ImportantInsnVisitor extends ClassVisitor { + + public class ImportantInsnMethodVisitor extends MethodVisitor { + + MethodVisitor delegate; + + public ImportantInsnMethodVisitor(int access, String name, String desc, String signature, String[] exceptions) { + super(Opcodes.ASM4, new MethodNode(access, name, desc, signature, exceptions)); + delegate = cv.visitMethod(access, name, desc, signature, exceptions); + } + + @Override + public void visitEnd() { + super.visitEnd(); + MethodNode mnode = (MethodNode) mv; + mnode.instructions = InsnComparator.getImportantList(mnode.instructions); + mnode.accept(delegate); + } + } + + public ImportantInsnVisitor(ClassVisitor cv) { + super(Opcodes.ASM4, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new ImportantInsnMethodVisitor(access, name, desc, signature, exceptions); + } +} diff --git a/src/main/java/codechicken/lib/asm/InsnComparator.java b/src/main/java/codechicken/lib/asm/InsnComparator.java new file mode 100644 index 0000000..f88da92 --- /dev/null +++ b/src/main/java/codechicken/lib/asm/InsnComparator.java @@ -0,0 +1,210 @@ +package codechicken.lib.asm; + +import static org.objectweb.asm.tree.AbstractInsnNode.FIELD_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.FRAME; +import static org.objectweb.asm.tree.AbstractInsnNode.IINC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.INT_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LABEL; +import static org.objectweb.asm.tree.AbstractInsnNode.LDC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LINE; +import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TYPE_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.VAR_INSN; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +import com.google.common.base.Function; +import com.google.common.collect.Maps; + +public class InsnComparator { + + public static boolean varInsnEqual(VarInsnNode insn1, VarInsnNode insn2) { + return insn1.var == -1 || insn2.var == -1 || insn1.var == insn2.var; + } + + public static boolean methodInsnEqual(MethodInsnNode insn1, MethodInsnNode insn2) { + return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc); + } + + public static boolean fieldInsnEqual(FieldInsnNode insn1, FieldInsnNode insn2) { + return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc); + } + + public static boolean ldcInsnEqual(LdcInsnNode insn1, LdcInsnNode insn2) { + return insn1.cst == null || insn2.cst == null || insn1.cst.equals(insn2.cst); + } + + public static boolean typeInsnEqual(TypeInsnNode insn1, TypeInsnNode insn2) { + return insn1.desc.equals("*") || insn2.desc.equals("*") || insn1.desc.equals(insn2.desc); + } + + public static boolean iincInsnEqual(IincInsnNode node1, IincInsnNode node2) { + return node1.var == node2.var && node1.incr == node2.incr; + } + + public static boolean intInsnEqual(IntInsnNode node1, IntInsnNode node2) { + return node1.operand == -1 || node2.operand == -1 || node1.operand == node2.operand; + } + + public static boolean insnEqual(AbstractInsnNode node1, AbstractInsnNode node2) { + if (node1.getOpcode() != node2.getOpcode()) return false; + + switch (node2.getType()) { + case VAR_INSN: + return varInsnEqual((VarInsnNode) node1, (VarInsnNode) node2); + case TYPE_INSN: + return typeInsnEqual((TypeInsnNode) node1, (TypeInsnNode) node2); + case FIELD_INSN: + return fieldInsnEqual((FieldInsnNode) node1, (FieldInsnNode) node2); + case METHOD_INSN: + return methodInsnEqual((MethodInsnNode) node1, (MethodInsnNode) node2); + case LDC_INSN: + return ldcInsnEqual((LdcInsnNode) node1, (LdcInsnNode) node2); + case IINC_INSN: + return iincInsnEqual((IincInsnNode) node1, (IincInsnNode) node2); + case INT_INSN: + return intInsnEqual((IntInsnNode) node1, (IntInsnNode) node2); + default: + return true; + } + } + + public static boolean insnImportant(AbstractInsnNode insn, Set controlFlowLabels) { + switch (insn.getType()) { + case LINE: + case FRAME: + return false; + case LABEL: + return controlFlowLabels.contains(insn); + default: + return true; + } + } + + public static Set getControlFlowLabels(InsnListSection list) { + return getControlFlowLabels(list.list); + } + + public static Set getControlFlowLabels(InsnList list) { + HashSet controlFlowLabels = new HashSet(); + for (AbstractInsnNode insn = list.getFirst(); insn != null; insn = insn.getNext()) { + switch (insn.getType()) { + case JUMP_INSN: + JumpInsnNode jinsn = (JumpInsnNode) insn; + controlFlowLabels.add(jinsn.label); + break; + case TABLESWITCH_INSN: + TableSwitchInsnNode tsinsn = (TableSwitchInsnNode) insn; + controlFlowLabels.add(tsinsn.dflt); + for (LabelNode label : tsinsn.labels) controlFlowLabels.add(label); + break; + case LOOKUPSWITCH_INSN: + LookupSwitchInsnNode lsinsn = (LookupSwitchInsnNode) insn; + controlFlowLabels.add(lsinsn.dflt); + for (LabelNode label : lsinsn.labels) controlFlowLabels.add(label); + break; + } + } + return controlFlowLabels; + } + + public static InsnList getImportantList(InsnList list) { + return getImportantList(new InsnListSection(list)).list; + } + + public static InsnListSection getImportantList(InsnListSection list) { + if (list.size() == 0) return list; + + Set controlFlowLabels = getControlFlowLabels(list); + Map labelMap = Maps.asMap(controlFlowLabels, new Function() { + + @Override + public LabelNode apply(LabelNode input) { + return input; + } + }); + + InsnListSection importantNodeList = new InsnListSection(); + for (AbstractInsnNode insn : list) + if (insnImportant(insn, controlFlowLabels)) importantNodeList.add(insn.clone(labelMap)); + + return importantNodeList; + } + + public static List find(InsnListSection haystack, InsnListSection needle) { + Set controlFlowLabels = getControlFlowLabels(haystack); + LinkedList list = new LinkedList(); + for (int start = 0; start <= haystack.size() - needle.size(); start++) { + InsnListSection section = matches(haystack.drop(start), needle, controlFlowLabels); + if (section != null) { + list.add(section); + start = section.end - 1; + } + } + + return list; + } + + public static List find(InsnList haystack, InsnListSection needle) { + return find(new InsnListSection(haystack), needle); + } + + public static InsnListSection matches(InsnListSection haystack, InsnListSection needle, + Set controlFlowLabels) { + int h = 0, n = 0; + for (; h < haystack.size() && n < needle.size(); h++) { + AbstractInsnNode insn = haystack.get(h); + if (!insnImportant(insn, controlFlowLabels)) continue; + + if (!insnEqual(haystack.get(h), needle.get(n))) return null; + n++; + } + if (n != needle.size()) return null; + + return haystack.take(h); + } + + public static InsnListSection findOnce(InsnListSection haystack, InsnListSection needle) { + List list = find(haystack, needle); + if (list.size() != 1) throw new RuntimeException( + "Needle found " + list.size() + " times in Haystack:\n" + haystack + "\n\n" + needle); + + return list.get(0); + } + + public static InsnListSection findOnce(InsnList haystack, InsnListSection needle) { + return findOnce(new InsnListSection(haystack), needle); + } + + public static List findN(InsnListSection haystack, InsnListSection needle) { + List list = find(haystack, needle); + if (list.isEmpty()) throw new RuntimeException("Needle not found in Haystack:\n" + haystack + "\n\n" + needle); + + return list; + } + + public static List findN(InsnList haystack, InsnListSection needle) { + return findN(new InsnListSection(haystack), needle); + } +} diff --git a/src/main/java/codechicken/lib/asm/InsnListSection.java b/src/main/java/codechicken/lib/asm/InsnListSection.java new file mode 100644 index 0000000..dbe204a --- /dev/null +++ b/src/main/java/codechicken/lib/asm/InsnListSection.java @@ -0,0 +1,235 @@ +package codechicken.lib.asm; + +import static org.objectweb.asm.tree.AbstractInsnNode.FRAME; +import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LABEL; +import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FrameNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceMethodVisitor; + +/** + * A section of an InsnList, may become invalid if the insn list is modified + */ +public class InsnListSection implements Iterable { + + private class InsnListSectionIterator implements Iterator { + + int i = 0; + + @Override + public boolean hasNext() { + return i < size(); + } + + @Override + public AbstractInsnNode next() { + return get(i++); + } + + @Override + public void remove() { + InsnListSection.this.remove(--i); + } + } + + public InsnList list; + public int start; + public int end; + + public InsnListSection(InsnList list, int start, int end) { + this.list = list; + this.start = start; + this.end = end; + } + + public InsnListSection(InsnList list, AbstractInsnNode first, AbstractInsnNode last) { + this(list, list.indexOf(first), list.indexOf(last) + 1); + } + + public InsnListSection(InsnList list) { + this(list, 0, list.size()); + } + + public InsnListSection() { + this(new InsnList()); + } + + public void accept(MethodVisitor mv) { + for (AbstractInsnNode insn : this) insn.accept(mv); + } + + public AbstractInsnNode getFirst() { + return size() == 0 ? null : list.get(start); + } + + public AbstractInsnNode getLast() { + return size() == 0 ? null : list.get(end - 1); + } + + public int size() { + return end - start; + } + + public AbstractInsnNode get(int i) { + return list.get(start + i); + } + + public void set(int i, AbstractInsnNode insn) { + list.set(get(i), insn); + } + + public void remove(int i) { + list.remove(get(i)); + end--; + } + + public void replace(AbstractInsnNode location, AbstractInsnNode insn) { + list.set(location, insn); + } + + public void add(AbstractInsnNode insn) { + list.add(insn); + end++; + } + + public void insertBefore(InsnList insns) { + int s = insns.size(); + if (this.list.size() == 0) list.insert(insns); + else list.insertBefore(list.get(start), insns); + start += s; + end += s; + } + + public void insert(InsnList insns) { + if (end == 0) list.insert(insns); + else list.insert(list.get(end - 1), insns); + } + + public void replace(InsnList insns) { + int s = insns.size(); + remove(); + insert(insns); + end = start + s; + } + + public void remove() { + while (end != start) remove(0); + } + + public void setLast(AbstractInsnNode last) { + end = list.indexOf(last) + 1; + } + + public void setFirst(AbstractInsnNode first) { + start = list.indexOf(first); + } + + public InsnListSection drop(int n) { + return slice(n, size()); + } + + public InsnListSection take(int n) { + return slice(0, n); + } + + public InsnListSection slice(int start, int end) { + return new InsnListSection(list, this.start + start, this.start + end); + } + + /** + * Removes leading and trailing labels and line number nodes that don't affect control flow + * + * @return this + */ + public InsnListSection trim(Set controlFlowLabels) { + while (start < end && !InsnComparator.insnImportant(getFirst(), controlFlowLabels)) start++; + + while (start < end && !InsnComparator.insnImportant(getLast(), controlFlowLabels)) end--; + + return this; + } + + public String toString() { + Textifier t = new Textifier(); + accept(new TraceMethodVisitor(t)); + StringWriter sw = new StringWriter(); + t.print(new PrintWriter(sw)); + return sw.toString(); + } + + public void println() { + System.out.println(toString()); + } + + public HashMap identityLabelMap() { + HashMap labelMap = new HashMap(); + for (AbstractInsnNode insn : this) switch (insn.getType()) { + case LABEL: + labelMap.put((LabelNode) insn, (LabelNode) insn); + break; + case JUMP_INSN: + labelMap.put(((JumpInsnNode) insn).label, ((JumpInsnNode) insn).label); + break; + case LOOKUPSWITCH_INSN: + LookupSwitchInsnNode linsn = (LookupSwitchInsnNode) insn; + labelMap.put(linsn.dflt, linsn.dflt); + for (LabelNode label : linsn.labels) labelMap.put(label, label); + break; + case TABLESWITCH_INSN: + TableSwitchInsnNode tinsn = (TableSwitchInsnNode) insn; + labelMap.put(tinsn.dflt, tinsn.dflt); + for (LabelNode label : tinsn.labels) labelMap.put(label, label); + break; + case FRAME: + FrameNode fnode = (FrameNode) insn; + if (fnode.local != null) + for (Object o : fnode.local) if (o instanceof LabelNode) labelMap.put((LabelNode) o, (LabelNode) o); + if (fnode.stack != null) + for (Object o : fnode.stack) if (o instanceof LabelNode) labelMap.put((LabelNode) o, (LabelNode) o); + break; + } + + return labelMap; + } + + public Map cloneLabels() { + Map labelMap = identityLabelMap(); + for (Entry entry : labelMap.entrySet()) entry.setValue(new LabelNode()); + + return labelMap; + } + + public InsnListSection copy() { + return copy(cloneLabels()); + } + + public InsnListSection copy(Map labelMap) { + InsnListSection copy = new InsnListSection(); + for (AbstractInsnNode insn : this) copy.add(insn.clone(labelMap)); + + return copy; + } + + @Override + public Iterator iterator() { + return new InsnListSectionIterator(); + } +} diff --git a/src/main/java/codechicken/lib/asm/LocalVariablesSorterVisitor.java b/src/main/java/codechicken/lib/asm/LocalVariablesSorterVisitor.java new file mode 100644 index 0000000..0157384 --- /dev/null +++ b/src/main/java/codechicken/lib/asm/LocalVariablesSorterVisitor.java @@ -0,0 +1,37 @@ +package codechicken.lib.asm; + +import java.util.Set; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.LocalVariablesSorter; + +public class LocalVariablesSorterVisitor extends ClassVisitor { + + public Set methods; + public String owner; + + public LocalVariablesSorterVisitor(Set methods, ClassVisitor cv) { + super(Opcodes.ASM4, cv); + this.methods = methods; + } + + public LocalVariablesSorterVisitor(ClassVisitor cv) { + this(null, cv); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + owner = name; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + return methods == null || methods.contains(new ObfMapping(owner, name, desc)) + ? new LocalVariablesSorter(access, desc, mv) + : mv; + } +} diff --git a/src/main/java/codechicken/lib/asm/ModularASMTransformer.java b/src/main/java/codechicken/lib/asm/ModularASMTransformer.java new file mode 100644 index 0000000..83bd830 --- /dev/null +++ b/src/main/java/codechicken/lib/asm/ModularASMTransformer.java @@ -0,0 +1,297 @@ +package codechicken.lib.asm; + +import static codechicken.lib.asm.ASMHelper.config; +import static codechicken.lib.asm.ASMHelper.createBytes; +import static codechicken.lib.asm.ASMHelper.dump; +import static codechicken.lib.asm.ASMHelper.findMethod; +import static codechicken.lib.asm.ASMHelper.logger; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; + +public class ModularASMTransformer { + + public static class ClassNodeTransformerList { + + List transformers = new LinkedList(); + HashSet methodsToSort = new HashSet(); + + public void add(ClassNodeTransformer t) { + transformers.add(t); + t.addMethodsToSort(methodsToSort); + } + + public byte[] transform(byte[] bytes) { + ClassNode cnode = new ClassNode(); + ClassReader reader = new ClassReader(bytes); + ClassVisitor cv = cnode; + if (!methodsToSort.isEmpty()) cv = new LocalVariablesSorterVisitor(methodsToSort, cv); + reader.accept(cv, ClassReader.EXPAND_FRAMES); + + try { + int writeFlags = 0; + for (ClassNodeTransformer t : transformers) { + t.transform(cnode); + writeFlags |= t.writeFlags; + } + + bytes = createBytes(cnode, writeFlags); + if (config.getTag("dump_asm").getBooleanValue(false)) + dump(bytes, new File("asm/ccl_modular/" + cnode.name.replace('/', '#') + ".txt"), false, false); + return bytes; + } catch (RuntimeException e) { + dump(bytes, new File("asm/ccl_modular/" + cnode.name.replace('/', '#') + ".txt"), false, false); + throw e; + } + } + } + + public abstract static class ClassNodeTransformer { + + public int writeFlags; + + public ClassNodeTransformer(int writeFlags) { + this.writeFlags = writeFlags; + } + + public ClassNodeTransformer() { + this(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + } + + public abstract String className(); + + public abstract void transform(ClassNode cnode); + + public void addMethodsToSort(Set set) {} + } + + public abstract static class MethodTransformer extends ClassNodeTransformer { + + public final ObfMapping method; + + public MethodTransformer(ObfMapping method) { + this.method = method.toClassloading(); + } + + @Override + public String className() { + return method.javaClass(); + } + + @Override + public void transform(ClassNode cnode) { + MethodNode mv = findMethod(method, cnode); + if (mv == null) throw new RuntimeException("Method not found: " + method); + + try { + transform(mv); + } catch (Exception e) { + throw new RuntimeException("Error transforming method: " + method, e); + } + } + + public abstract void transform(MethodNode mv); + } + + public static class MethodWriter extends ClassNodeTransformer { + + public final int access; + public final ObfMapping method; + public final String[] exceptions; + public InsnList list; + + public MethodWriter(int access, ObfMapping method) { + this(access, method, null, (InsnList) null); + } + + public MethodWriter(int access, ObfMapping method, InsnList list) { + this(access, method, null, list); + } + + public MethodWriter(int access, ObfMapping method, ASMBlock block) { + this(access, method, null, block); + } + + public MethodWriter(int access, ObfMapping method, String[] exceptions) { + this(access, method, exceptions, (InsnList) null); + } + + public MethodWriter(int access, ObfMapping method, String[] exceptions, InsnList list) { + this.access = access; + this.method = method.toClassloading(); + this.exceptions = exceptions; + this.list = list; + } + + public MethodWriter(int access, ObfMapping method, String[] exceptions, ASMBlock block) { + this(access, method, exceptions, block.rawListCopy()); + } + + @Override + public String className() { + return method.javaClass(); + } + + @Override + public void transform(ClassNode cnode) { + MethodNode mv = findMethod(method, cnode); + if (mv == null) mv = (MethodNode) method.visitMethod(cnode, access, exceptions); + else { + mv.access = access; + mv.instructions.clear(); + if (mv.localVariables != null) mv.localVariables.clear(); + if (mv.tryCatchBlocks != null) mv.tryCatchBlocks.clear(); + } + + write(mv); + } + + public void write(MethodNode mv) { + logger.debug("Writing method " + method); + list.accept(mv); + } + } + + public static class MethodInjector extends MethodTransformer { + + public ASMBlock needle; + public ASMBlock injection; + public boolean before; + + public MethodInjector(ObfMapping method, ASMBlock needle, ASMBlock injection, boolean before) { + super(method); + this.needle = needle; + this.injection = injection; + this.before = before; + } + + public MethodInjector(ObfMapping method, ASMBlock injection, boolean before) { + this(method, null, injection, before); + } + + public MethodInjector(ObfMapping method, InsnList needle, InsnList injection, boolean before) { + this(method, new ASMBlock(needle), new ASMBlock(injection), before); + } + + public MethodInjector(ObfMapping method, InsnList injection, boolean before) { + this(method, null, new ASMBlock(injection), before); + } + + @Override + public void addMethodsToSort(Set set) { + set.add(method); + } + + @Override + public void transform(MethodNode mv) { + if (needle == null) { + logger.debug("Injecting " + (before ? "before" : "after") + " method " + method); + if (before) mv.instructions.insert(injection.rawListCopy()); + else mv.instructions.add(injection.rawListCopy()); + } else { + for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) { + logger.debug( + "Injecting " + (before ? "before" : "after") + + " method " + + method + + " @ " + + key.start + + " - " + + key.end); + ASMBlock injectBlock = injection.copy().mergeLabels(needle.applyLabels(key)); + + if (before) key.insertBefore(injectBlock.list.list); + else key.insert(injectBlock.list.list); + } + } + } + } + + public static class MethodReplacer extends MethodTransformer { + + public ASMBlock needle; + public ASMBlock replacement; + + public MethodReplacer(ObfMapping method, ASMBlock needle, ASMBlock replacement) { + super(method); + this.needle = needle; + this.replacement = replacement; + } + + public MethodReplacer(ObfMapping method, InsnList needle, InsnList replacement) { + this(method, new ASMBlock(needle), new ASMBlock(replacement)); + } + + @Override + public void addMethodsToSort(Set set) { + set.add(method); + } + + @Override + public void transform(MethodNode mv) { + for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) { + logger.debug("Replacing method " + method + " @ " + key.start + " - " + key.end); + ASMBlock replaceBlock = replacement.copy().pullLabels(needle.applyLabels(key)); + key.insert(replaceBlock.list.list); + } + } + } + + public static class FieldWriter extends ClassNodeTransformer { + + public final ObfMapping field; + public final int access; + public final Object value; + + public FieldWriter(int access, ObfMapping field, Object value) { + this.field = field.toClassloading(); + this.access = access; + this.value = value; + } + + public FieldWriter(int access, ObfMapping field) { + this(access, field, null); + } + + @Override + public String className() { + return field.javaClass(); + } + + @Override + public void transform(ClassNode cnode) { + field.visitField(cnode, access, value); + } + } + + public HashMap transformers = new HashMap<>(); + + public void add(ClassNodeTransformer t) { + ClassNodeTransformerList list = transformers.get(t.className()); + if (list == null) { + transformers.put(t.className(), list = new ClassNodeTransformerList()); + } + list.add(t); + } + + public boolean isEmpty() { + return transformers.isEmpty(); + } + + public byte[] transform(String name, byte[] bytes) { + if (bytes == null) return null; + ClassNodeTransformerList list = transformers.get(name); + return list == null ? bytes : list.transform(bytes); + } +} diff --git a/src/main/java/codechicken/lib/asm/ObfMapping.java b/src/main/java/codechicken/lib/asm/ObfMapping.java new file mode 100644 index 0000000..27ca913 --- /dev/null +++ b/src/main/java/codechicken/lib/asm/ObfMapping.java @@ -0,0 +1,401 @@ +package codechicken.lib.asm; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.swing.JFileChooser; + +import net.minecraft.launchwrapper.Launch; +import net.minecraftforge.common.ForgeVersion; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; + +import com.google.common.base.Charsets; +import com.google.common.base.Objects; +import com.google.common.io.LineProcessor; +import com.google.common.io.Resources; + +import codechicken.lib.config.ConfigTag; +import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; +import cpw.mods.fml.relauncher.FMLInjectionData; + +public class ObfMapping { + + public static class ObfRemapper extends Remapper { + + private final HashMap fields = new HashMap<>(); + private final HashMap funcs = new HashMap<>(); + + public ObfRemapper() { + try { + Field rawFieldMapsField = FMLDeobfuscatingRemapper.class.getDeclaredField("rawFieldMaps"); + Field rawMethodMapsField = FMLDeobfuscatingRemapper.class.getDeclaredField("rawMethodMaps"); + rawFieldMapsField.setAccessible(true); + rawMethodMapsField.setAccessible(true); + Map> rawFieldMaps = (Map>) rawFieldMapsField + .get(FMLDeobfuscatingRemapper.INSTANCE); + Map> rawMethodMaps = (Map>) rawMethodMapsField + .get(FMLDeobfuscatingRemapper.INSTANCE); + + if (rawFieldMaps == null) throw new IllegalStateException( + "codechicken.lib.asm.ObfMapping loaded too early. Make sure all references are in or after the asm transformer load stage"); + + for (Map map : rawFieldMaps.values()) + for (Entry entry : map.entrySet()) if (entry.getValue().startsWith("field")) + fields.put(entry.getValue(), entry.getKey().substring(0, entry.getKey().indexOf(':'))); + for (Map map : rawMethodMaps.values()) + for (Entry entry : map.entrySet()) if (entry.getValue().startsWith("func")) + funcs.put(entry.getValue(), entry.getKey().substring(0, entry.getKey().indexOf('('))); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String mapMethodName(String owner, String name, String desc) { + String s = funcs.get(name); + return s == null ? name : s; + } + + @Override + public String mapFieldName(String owner, String name, String desc) { + String s = fields.get(name); + return s == null ? name : s; + } + + @Override + public String map(String typeName) { + return FMLDeobfuscatingRemapper.INSTANCE.unmap(typeName); + } + + public String unmap(String typeName) { + return FMLDeobfuscatingRemapper.INSTANCE.map(typeName); + } + + public boolean isObf(String typeName) { + return !map(typeName).equals(typeName) || !unmap(typeName).equals(typeName); + } + } + + public static class MCPRemapper extends Remapper implements LineProcessor { + + public static File[] getConfFiles() { + ConfigTag tag = ASMHelper.config.getTag("mappingDir") + .setComment("Path to directory holding packaged.srg, fields.csv and methods.csv for mcp remapping"); + for (int i = 0; i < DIR_GUESSES + DIR_ASKS; i++) { + File dir = confDirectoryGuess(i, tag); + if (dir == null || dir.isFile()) continue; + + File[] mappings; + try { + mappings = parseConfDir(dir); + } catch (Exception e) { + if (i >= DIR_GUESSES) e.printStackTrace(); + continue; + } + + tag.setValue(dir.getPath()); + return mappings; + } + + throw new RuntimeException("Failed to select mappings directory, set it manually in the config"); + } + + private static final int DIR_GUESSES = 6; + private static final int DIR_ASKS = 3; + + public static File confDirectoryGuess(int i, ConfigTag tag) { + File mcDir = (File) FMLInjectionData.data()[6]; + switch (i) { + case 0: + return tag.value != null ? new File(tag.getValue()) : null; + case 1: + return new File(mcDir, "../conf"); + case 2: + return new File(mcDir, "../build/unpacked/conf"); + case 3: + return new File( + System.getProperty("user.home"), + ".gradle/caches/minecraft/net/minecraftforge/forge/" + FMLInjectionData.data()[4] + + "-" + + ForgeVersion.getVersion() + + "/unpacked/conf"); + case 4: + return new File( + System.getProperty("user.home"), + ".gradle/caches/minecraft/net/minecraftforge/forge/" + FMLInjectionData.data()[4] + + "-" + + ForgeVersion.getVersion() + + "-" + + FMLInjectionData.data()[4] + + "/unpacked/conf"); + case 5: + final String gradleCsvDir = System.getProperty("net.minecraftforge.gradle.GradleStart.csvDir"); + return gradleCsvDir != null ? new File(gradleCsvDir) : null; + default: + JFileChooser fc = new JFileChooser(mcDir); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setDialogTitle("Select an mcp conf dir for the deobfuscator."); + int ret = fc.showDialog(null, "Select"); + return ret == JFileChooser.APPROVE_OPTION ? fc.getSelectedFile() : null; + } + } + + public static File[] parseConfDir(File confDir) { + File srgDir = new File(confDir, "conf"); + if (!srgDir.exists()) srgDir = confDir; + + File srgs = new File(srgDir, "packaged.srg"); + if (!srgs.exists()) srgs = new File(srgDir, "joined.srg"); + if (!srgs.exists()) throw new RuntimeException("Could not find packaged.srg or joined.srg"); + + File mapDir = new File(confDir, "mappings"); + if (!mapDir.exists()) mapDir = confDir; + + File methods = new File(mapDir, "methods.csv"); + if (!methods.exists()) throw new RuntimeException("Could not find methods.csv"); + File fields = new File(mapDir, "fields.csv"); + if (!fields.exists()) throw new RuntimeException("Could not find fields.csv"); + + return new File[] { srgs, methods, fields }; + } + + private final HashMap fields = new HashMap<>(); + private final HashMap funcs = new HashMap<>(); + + public MCPRemapper() { + File[] mappings = getConfFiles(); + try { + Resources.readLines(mappings[1].toURI().toURL(), Charsets.UTF_8, this); + Resources.readLines(mappings[2].toURI().toURL(), Charsets.UTF_8, this); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public String mapMethodName(String owner, String name, String desc) { + String s = funcs.get(name); + return s == null ? name : s; + } + + @Override + public String mapFieldName(String owner, String name, String desc) { + String s = fields.get(name); + return s == null ? name : s; + } + + @Override + public boolean processLine(String line) throws IOException { + int i = line.indexOf(','); + String srg = line.substring(0, i); + int i2 = i + 1; + i = line.indexOf(',', i2); + String mcp = line.substring(i2, i); + (srg.startsWith("func") ? funcs : fields).put(srg, mcp); + return true; + } + + @Override + public Void getResult() { + return null; + } + } + + public static ObfRemapper obfMapper = new ObfRemapper(); + public static Remapper mcpMapper = null; + + public static void loadMCPRemapper() { + if (mcpMapper == null) mcpMapper = new MCPRemapper(); + } + + public static final boolean obfuscated; + + static { + boolean obf = true; + try { + obf = Launch.classLoader.getClassBytes("net.minecraft.world.World") == null; + } catch (IOException ignored) {} + obfuscated = obf; + if (!obf) loadMCPRemapper(); + } + + public String s_owner; + public String s_name; + public String s_desc; + + public ObfMapping(String owner) { + this(owner, "", ""); + } + + public ObfMapping(String owner, String name, String desc) { + this.s_owner = owner; + this.s_name = name; + this.s_desc = desc; + + if (s_owner.contains(".")) throw new IllegalArgumentException(s_owner); + } + + public ObfMapping(ObfMapping descmap, String subclass) { + this(subclass, descmap.s_name, descmap.s_desc); + } + + public static ObfMapping fromDesc(String s) { + int lastDot = s.lastIndexOf('.'); + if (lastDot < 0) return new ObfMapping(s, "", ""); + int sep = s.indexOf('('); // methods + int sep_end = sep; + if (sep < 0) { + sep = s.indexOf(' '); // some stuffs + sep_end = sep + 1; + } + if (sep < 0) { + sep = s.indexOf(':'); // fields + sep_end = sep + 1; + } + if (sep < 0) return new ObfMapping(s.substring(0, lastDot), s.substring(lastDot + 1), ""); + + return new ObfMapping(s.substring(0, lastDot), s.substring(lastDot + 1, sep), s.substring(sep_end)); + } + + public ObfMapping subclass(String subclass) { + return new ObfMapping(this, subclass); + } + + public boolean matches(MethodNode node) { + return s_name.equals(node.name) && s_desc.equals(node.desc); + } + + public boolean matches(MethodInsnNode node) { + return s_owner.equals(node.owner) && s_name.equals(node.name) && s_desc.equals(node.desc); + } + + public AbstractInsnNode toInsn(int opcode) { + if (isClass()) return new TypeInsnNode(opcode, s_owner); + else if (isMethod()) return new MethodInsnNode(opcode, s_owner, s_name, s_desc); + else return new FieldInsnNode(opcode, s_owner, s_name, s_desc); + } + + public void visitTypeInsn(MethodVisitor mv, int opcode) { + mv.visitTypeInsn(opcode, s_owner); + } + + public void visitMethodInsn(MethodVisitor mv, int opcode) { + mv.visitMethodInsn(opcode, s_owner, s_name, s_desc); + } + + public void visitFieldInsn(MethodVisitor mv, int opcode) { + mv.visitFieldInsn(opcode, s_owner, s_name, s_desc); + } + + public MethodVisitor visitMethod(ClassVisitor visitor, int access, String[] exceptions) { + return visitor.visitMethod(access, s_name, s_desc, null, exceptions); + } + + public FieldVisitor visitField(ClassVisitor visitor, int access, Object value) { + return visitor.visitField(access, s_name, s_desc, null, value); + } + + public boolean isClass(String name) { + return name.replace('.', '/').equals(s_owner); + } + + public boolean matches(String name, String desc) { + return s_name.equals(name) && s_desc.equals(desc); + } + + public boolean matches(FieldNode node) { + return s_name.equals(node.name) && s_desc.equals(node.desc); + } + + public boolean matches(FieldInsnNode node) { + return s_owner.equals(node.owner) && s_name.equals(node.name) && s_desc.equals(node.desc); + } + + public String javaClass() { + return s_owner.replace('/', '.'); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ObfMapping)) return false; + + ObfMapping desc = (ObfMapping) obj; + return s_owner.equals(desc.s_owner) && s_name.equals(desc.s_name) && s_desc.equals(desc.s_desc); + } + + @Override + public int hashCode() { + return Objects.hashCode(s_desc, s_name, s_owner); + } + + @Override + public String toString() { + if (s_name.length() == 0) return "[" + s_owner + "]"; + if (s_desc.length() == 0) return "[" + s_owner + "." + s_name + "]"; + return "[" + (isMethod() ? methodDesc() : fieldDesc()) + "]"; + } + + public String methodDesc() { + return s_owner + "." + s_name + s_desc; + } + + public String fieldDesc() { + return s_owner + "." + s_name + ":" + s_desc; + } + + public boolean isClass() { + return s_name.length() == 0; + } + + public boolean isMethod() { + return s_desc.contains("("); + } + + public boolean isField() { + return !isClass() && !isMethod(); + } + + public ObfMapping map(Remapper mapper) { + if (mapper == null) return this; + + if (isMethod()) s_name = mapper.mapMethodName(s_owner, s_name, s_desc); + else if (isField()) s_name = mapper.mapFieldName(s_owner, s_name, s_desc); + + s_owner = mapper.mapType(s_owner); + + if (isMethod()) s_desc = mapper.mapMethodDesc(s_desc); + else if (s_desc.length() > 0) s_desc = mapper.mapDesc(s_desc); + + return this; + } + + public ObfMapping toRuntime() { + map(mcpMapper); + return this; + } + + public ObfMapping toClassloading() { + if (!obfuscated) map(mcpMapper); + else if (obfMapper.isObf(s_owner)) map(obfMapper); + return this; + } + + public ObfMapping copy() { + return new ObfMapping(s_owner, s_name, s_desc); + } +} diff --git a/src/main/java/codechicken/lib/asm/RedirectorTransformer.java b/src/main/java/codechicken/lib/asm/RedirectorTransformer.java new file mode 100644 index 0000000..fdda8b5 --- /dev/null +++ b/src/main/java/codechicken/lib/asm/RedirectorTransformer.java @@ -0,0 +1,205 @@ +package codechicken.lib.asm; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.Launch; + +import org.apache.commons.io.FileUtils; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceClassVisitor; + +import codechicken.core.launch.CodeChickenCorePlugin; + +public class RedirectorTransformer implements IClassTransformer, Opcodes { + + private static final boolean DUMP_CLASSES = Boolean.parseBoolean(System.getProperty("ccl.dumpClass", "false")); + private static final String RenderStateClass = "codechicken/lib/render/CCRenderState"; + private static final Set redirectedFields = new HashSet<>(); + private static final Set redirectedSimpleMethods = new HashSet<>(); + private static final Set redirectedMethods = new HashSet<>(); + private static final ClassConstantPoolParser cstPoolParser; + + static { + Collections.addAll( + redirectedFields, + "pipeline", + "model", + "firstVertexIndex", + "lastVertexIndex", + "vertexIndex", + "baseColour", + "alphaOverride", + "useNormals", + "computeLighting", + "useColour", + "lightMatrix", + "vert", + "hasNormal", + "normal", + "hasColour", + "colour", + "hasBrightness", + "brightness", + "side", + "lc", + "normalAttrib", + "colourAttrib", + "lightCoordAttrib", + "sideAttrib", + "lightingAttrib" + + ); + Collections.addAll(redirectedSimpleMethods, "reset", "pullLightmap", "pushLightmap", "setDynamic", "draw"); + Collections.addAll( + redirectedMethods, + "setPipeline", + "bindModel", + "setModel", + "setVertexRange", + "render", + "runPipeline", + "writeVert", + "setNormal", + "setColour", + "setBrightness", + "startDrawing"); + + cstPoolParser = new ClassConstantPoolParser(RenderStateClass); + } + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (!cstPoolParser.find(basicClass)) { + return basicClass; + } + + final ClassReader cr = new ClassReader(basicClass); + final ClassNode cn = new ClassNode(); + cr.accept(cn, 0); + boolean changed = false; + + // spotless:off + for (MethodNode mn : cn.methods) { + for (AbstractInsnNode node : mn.instructions.toArray()) { + if (node instanceof FieldInsnNode fNode) { + if (node.getOpcode() == GETSTATIC && RenderStateClass.equals(fNode.owner) && redirectedFields.contains(fNode.name)) { + mn.instructions.insertBefore(fNode, + new MethodInsnNode( + INVOKESTATIC, + fNode.owner, + "instance", + "()Lcodechicken/lib/render/CCRenderState;", + false)); + fNode.setOpcode(GETFIELD); + changed = true; + } else if (node.getOpcode() == PUTSTATIC && RenderStateClass.equals(fNode.owner) && redirectedFields.contains(fNode.name)) { + InsnList list = new InsnList(); + list.add(new MethodInsnNode( + INVOKESTATIC, + fNode.owner, + "instance", + "()Lcodechicken/lib/render/CCRenderState;", + false)); + list.add(new InsnNode(SWAP)); + mn.instructions.insertBefore(fNode, list); + fNode.setOpcode(PUTFIELD); + changed = true; + } + } else if (node instanceof MethodInsnNode mNode) { + if (node.getOpcode() == INVOKESTATIC && RenderStateClass.equals(mNode.owner) && redirectedSimpleMethods.contains(mNode.name)) { + mn.instructions.insertBefore(mNode, + new MethodInsnNode( + INVOKESTATIC, + mNode.owner, + "instance", + "()Lcodechicken/lib/render/CCRenderState;", + false)); + mNode.setOpcode(INVOKEVIRTUAL); + mNode.name = mNode.name + "Instance"; + changed = true; + } else if (node.getOpcode() == INVOKEVIRTUAL && RenderStateClass.equals(mNode.owner) + && (redirectedSimpleMethods.contains(mNode.name) || redirectedMethods.contains(mNode.name))) { + // Handle mods that updated to previously new API + mNode.name = mNode.name + "Instance"; + changed = true; + } + } + } + } + // spotless:on + + if (changed) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + cn.accept(cw); + final byte[] bytes = cw.toByteArray(); + if (DUMP_CLASSES) { + saveTransformedClass(basicClass, transformedName + "_PRE"); + saveTransformedClass(bytes, transformedName + "_POST"); + } + return bytes; + } + return basicClass; + } + + private File outputDir = null; + + private void saveTransformedClass(final byte[] data, final String classname) { + if (outputDir == null) { + outputDir = new File(Launch.minecraftHome, "ASM_CCL" + File.separatorChar + "REDIRECTOR"); + try { + FileUtils.deleteDirectory(outputDir); + } catch (IOException ignored) {} + if (!outputDir.exists()) { + // noinspection ResultOfMethodCallIgnored + outputDir.mkdirs(); + } + } + final String fileName = classname.replace('.', File.separatorChar); + final File classFile = new File(outputDir, fileName + ".class"); + final File bytecodeFile = new File(outputDir, fileName + "_BYTE.txt"); + final File outDir = classFile.getParentFile(); + if (!outDir.exists()) { + // noinspection ResultOfMethodCallIgnored + outDir.mkdirs(); + } + if (classFile.exists()) { + // noinspection ResultOfMethodCallIgnored + classFile.delete(); + } + try (final OutputStream output = Files.newOutputStream(classFile.toPath())) { + output.write(data); + CodeChickenCorePlugin.logger.info("Saved class (byte[]) to " + classFile.toPath()); + } catch (IOException e) { + CodeChickenCorePlugin.logger.error("Could not save class (byte[]) " + classname); + } + if (bytecodeFile.exists()) { + // noinspection ResultOfMethodCallIgnored + bytecodeFile.delete(); + } + try (final OutputStream output = Files.newOutputStream(bytecodeFile.toPath())) { + final ClassReader classReader = new ClassReader(data); + classReader.accept(new TraceClassVisitor(null, new Textifier(), new PrintWriter(output)), 0); + CodeChickenCorePlugin.logger.info("Saved class (bytecode) to " + bytecodeFile.toPath()); + } catch (IOException e) { + CodeChickenCorePlugin.logger.error("Could not save class (bytecode) " + classname); + } + } +} diff --git a/src/main/java/codechicken/lib/colour/Colour.java b/src/main/java/codechicken/lib/colour/Colour.java new file mode 100644 index 0000000..c73a5e8 --- /dev/null +++ b/src/main/java/codechicken/lib/colour/Colour.java @@ -0,0 +1,158 @@ +package codechicken.lib.colour; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.lwjgl.opengl.GL11; + +import codechicken.lib.config.ConfigTag.IConfigType; +import codechicken.lib.math.MathHelper; +import codechicken.lib.util.Copyable; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public abstract class Colour implements Copyable { + + public static IConfigType configRGB = new IConfigType() { + + @Override + public String configValue(Colour entry) { + String s = Long.toString(((long) entry.rgb()) << 32 >>> 32, 16); + while (s.length() < 6) s = "0" + s; + return "0x" + s.toUpperCase(); + } + + private final Pattern patternRGB = Pattern.compile("(\\d+),(\\d+),(\\d+)"); + + @Override + public Colour valueOf(String text) throws Exception { + Matcher matcherRGB = patternRGB.matcher(text.replaceAll("\\s", "")); + if (matcherRGB.matches()) return new ColourRGBA( + Integer.parseInt(matcherRGB.group(1)), + Integer.parseInt(matcherRGB.group(2)), + Integer.parseInt(matcherRGB.group(3)), + 0xFF); + + int hex = (int) Long.parseLong(text.replace("0x", ""), 16); + return new ColourRGBA(hex << 8 | 0xFF); + } + }; + + public byte r; + public byte g; + public byte b; + public byte a; + + public Colour(int r, int g, int b, int a) { + this.r = (byte) r; + this.g = (byte) g; + this.b = (byte) b; + this.a = (byte) a; + } + + public Colour(Colour colour) { + r = colour.r; + g = colour.g; + b = colour.b; + a = colour.a; + } + + @SideOnly(Side.CLIENT) + public void glColour() { + GL11.glColor4ub(r, g, b, a); + } + + @SideOnly(Side.CLIENT) + public void glColour(int a) { + GL11.glColor4ub(r, g, b, (byte) a); + } + + public abstract int pack(); + + @Override + public String toString() { + return getClass().getSimpleName() + "[0x" + Integer.toHexString(pack()).toUpperCase() + "]"; + } + + public Colour add(Colour colour2) { + a += colour2.a; + r += colour2.r; + g += colour2.g; + b += colour2.b; + return this; + } + + public Colour sub(Colour colour2) { + int ia = (a & 0xFF) - (colour2.a & 0xFF); + int ir = (r & 0xFF) - (colour2.r & 0xFF); + int ig = (g & 0xFF) - (colour2.g & 0xFF); + int ib = (b & 0xFF) - (colour2.b & 0xFF); + a = (byte) (ia < 0 ? 0 : ia); + r = (byte) (ir < 0 ? 0 : ir); + g = (byte) (ig < 0 ? 0 : ig); + b = (byte) (ib < 0 ? 0 : ib); + return this; + } + + public Colour invert() { + a = (byte) (0xFF - (a & 0xFF)); + r = (byte) (0xFF - (r & 0xFF)); + g = (byte) (0xFF - (g & 0xFF)); + b = (byte) (0xFF - (b & 0xFF)); + return this; + } + + public Colour multiply(Colour colour2) { + a = (byte) ((a & 0xFF) * ((colour2.a & 0xFF) / 255D)); + r = (byte) ((r & 0xFF) * ((colour2.r & 0xFF) / 255D)); + g = (byte) ((g & 0xFF) * ((colour2.g & 0xFF) / 255D)); + b = (byte) ((b & 0xFF) * ((colour2.b & 0xFF) / 255D)); + return this; + } + + public Colour scale(double d) { + a = (byte) ((a & 0xFF) * d); + r = (byte) ((r & 0xFF) * d); + g = (byte) ((g & 0xFF) * d); + b = (byte) ((b & 0xFF) * d); + return this; + } + + public Colour interpolate(Colour colour2, double d) { + return this.add(colour2.copy().sub(this).scale(d)); + } + + public Colour multiplyC(double d) { + r = (byte) MathHelper.clip((r & 0xFF) * d, 0, 255); + g = (byte) MathHelper.clip((g & 0xFF) * d, 0, 255); + b = (byte) MathHelper.clip((b & 0xFF) * d, 0, 255); + + return this; + } + + public abstract Colour copy(); + + public int rgb() { + return (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF); + } + + public int argb() { + return (a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF); + } + + public int rgba() { + return (r & 0xFF) << 24 | (g & 0xFF) << 16 | (b & 0xFF) << 8 | (a & 0xFF); + } + + public Colour set(Colour colour) { + r = colour.r; + g = colour.g; + b = colour.b; + a = colour.a; + return this; + } + + public boolean equals(Colour colour) { + return colour != null && rgba() == colour.rgba(); + } +} diff --git a/src/main/java/codechicken/lib/colour/ColourARGB.java b/src/main/java/codechicken/lib/colour/ColourARGB.java new file mode 100644 index 0000000..56483de --- /dev/null +++ b/src/main/java/codechicken/lib/colour/ColourARGB.java @@ -0,0 +1,28 @@ +package codechicken.lib.colour; + +public class ColourARGB extends Colour { + + public ColourARGB(int colour) { + super((colour >> 16) & 0xFF, (colour >> 8) & 0xFF, colour & 0xFF, (colour >> 24) & 0xFF); + } + + public ColourARGB(int a, int r, int g, int b) { + super(r, g, b, a); + } + + public ColourARGB(ColourARGB colour) { + super(colour); + } + + public ColourARGB copy() { + return new ColourARGB(this); + } + + public int pack() { + return pack(this); + } + + public static int pack(Colour colour) { + return (colour.a & 0xFF) << 24 | (colour.r & 0xFF) << 16 | (colour.g & 0xFF) << 8 | (colour.b & 0xFF); + } +} diff --git a/src/main/java/codechicken/lib/colour/ColourRGBA.java b/src/main/java/codechicken/lib/colour/ColourRGBA.java new file mode 100644 index 0000000..a5ff30c --- /dev/null +++ b/src/main/java/codechicken/lib/colour/ColourRGBA.java @@ -0,0 +1,50 @@ +package codechicken.lib.colour; + +public class ColourRGBA extends Colour { + + public ColourRGBA(int colour) { + super((colour >> 24) & 0xFF, (colour >> 16) & 0xFF, (colour >> 8) & 0xFF, colour & 0xFF); + } + + public ColourRGBA(double r, double g, double b, double a) { + super((int) (255 * r), (int) (255 * g), (int) (255 * b), (int) (255 * a)); + } + + public ColourRGBA(int r, int g, int b, int a) { + super(r, g, b, a); + } + + public ColourRGBA(ColourRGBA colour) { + super(colour); + } + + public int pack() { + return pack(this); + } + + @Override + public Colour copy() { + return new ColourRGBA(this); + } + + public static int pack(Colour colour) { + return (colour.r & 0xFF) << 24 | (colour.g & 0xFF) << 16 | (colour.b & 0xFF) << 8 | (colour.a & 0xFF); + } + + public static int multiply(int c1, int c2) { + if (c1 == -1) return c2; + if (c2 == -1) return c1; + int r = (((c1 >>> 24) * (c2 >>> 24)) & 0xFF00) << 16; + int g = (((c1 >> 16 & 0xFF) * (c2 >> 16 & 0xFF)) & 0xFF00) << 8; + int b = ((c1 >> 8 & 0xFF) * (c2 >> 8 & 0xFF)) & 0xFF00; + int a = ((c1 & 0xFF) * (c2 & 0xFF)) >> 8; + return r | g | b | a; + } + + public static int multiplyC(int c, float f) { + int r = (int) ((c >>> 24) * f); + int g = (int) ((c >> 16 & 0xFF) * f); + int b = (int) ((c >> 8 & 0xFF) * f); + return r << 24 | g << 16 | b << 8 | c & 0xFF; + } +} diff --git a/src/main/java/codechicken/lib/colour/CustomGradient.java b/src/main/java/codechicken/lib/colour/CustomGradient.java new file mode 100644 index 0000000..4f228c7 --- /dev/null +++ b/src/main/java/codechicken/lib/colour/CustomGradient.java @@ -0,0 +1,32 @@ +package codechicken.lib.colour; + +import java.awt.image.BufferedImage; + +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.render.TextureUtils; + +public class CustomGradient { + + public int[] gradient; + + public CustomGradient(ResourceLocation textureFile) { + BufferedImage img = TextureUtils.loadBufferedImage(textureFile); + if (img == null) throw new RuntimeException("File not found: " + textureFile.toString()); + + int[] data = new int[img.getWidth()]; + img.getRGB(0, 0, img.getWidth(), 1, data, 0, img.getWidth()); + gradient = new int[img.getWidth()]; + for (int i = 0; i < data.length; i++) gradient[i] = (data[i] << 8) | (((data[i]) >> 24) & 0xFF); + } + + public ColourRGBA getColour(double position) { + return new ColourRGBA(getColourI(position)); + } + + public int getColourI(double position) { + int off = (int) MathHelper.clip(gradient.length * position, 0, gradient.length - 1); + return gradient[off]; + } +} diff --git a/src/main/java/codechicken/lib/config/ConfigFile.java b/src/main/java/codechicken/lib/config/ConfigFile.java new file mode 100644 index 0000000..31a18de --- /dev/null +++ b/src/main/java/codechicken/lib/config/ConfigFile.java @@ -0,0 +1,110 @@ +package codechicken.lib.config; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; + +public class ConfigFile extends ConfigTagParent { + + public static final byte[] crlf = new byte[] { 0xD, 0xA }; + + public File file; + private boolean loading; + + public ConfigFile(File file) { + newlinemode = 2; + load(file); + } + + protected ConfigFile() {} + + protected void load(File file) { + try { + if (!file.getParentFile().exists()) file.getParentFile().mkdirs(); + if (!file.exists()) file.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.file = file; + loadConfig(); + } + + protected void loadConfig() { + loading = true; + BufferedReader reader; + try { + reader = new BufferedReader(new FileReader(file)); + + while (true) { + reader.mark(2000); + String line = reader.readLine(); + if (line != null && line.startsWith("#")) { + if (comment == null || comment.equals("")) comment = line.substring(1); + else comment = comment + "\n" + line.substring(1); + } else { + reader.reset(); + break; + } + } + loadChildren(reader); + reader.close(); + + } catch (IOException e) { + throw new RuntimeException(e); + } + + loading = false; + } + + @Override + public ConfigFile setComment(String header) { + super.setComment(header); + return this; + } + + @Override + public ConfigFile setSortMode(int mode) { + super.setSortMode(mode); + return this; + } + + @Override + public String getNameQualifier() { + return ""; + } + + public static String readLine(BufferedReader reader) throws IOException { + String line = reader.readLine(); + return line == null ? null : line.replace("\t", ""); + } + + public static void writeLine(PrintWriter writer, String line, int tabs) { + for (int i = 0; i < tabs; i++) writer.print('\t'); + + writer.println(line); + } + + public void saveConfig() { + if (loading) return; + + PrintWriter writer; + try { + writer = new PrintWriter(file); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + + writeComment(writer, 0); + ConfigFile.writeLine(writer, "", 0); + saveTagTree(writer, 0, ""); + writer.flush(); + writer.close(); + } + + public boolean isLoading() { + return loading; + } +} diff --git a/src/main/java/codechicken/lib/config/ConfigTag.java b/src/main/java/codechicken/lib/config/ConfigTag.java new file mode 100644 index 0000000..29e8eb3 --- /dev/null +++ b/src/main/java/codechicken/lib/config/ConfigTag.java @@ -0,0 +1,225 @@ +package codechicken.lib.config; + +import java.io.PrintWriter; + +public class ConfigTag extends ConfigTagParent { + + public interface IConfigType { + + public String configValue(T entry); + + public T valueOf(String text) throws Exception; + } + + public ConfigTag(ConfigTagParent parent, String name) { + this.parent = parent; + this.name = name; + qualifiedname = parent.getNameQualifier() + name; + newline = parent.newlinemode == 2; + parent.addChild(this); + } + + @Override + public String getNameQualifier() { + return qualifiedname + "."; + } + + @Override + public void saveConfig() { + parent.saveConfig(); + } + + /** + * Called when the tag is loaded from a config file as opposed to constructed by a mod + * + * @return this + */ + public ConfigTag onLoaded() { + return this; + } + + public void setValue(String value) { + this.value = value; + saveConfig(); + } + + public void setDefaultValue(String defaultValue) { + if (value == null) { + value = defaultValue; + saveConfig(); + } + } + + public void setIntValue(int i) { + setValue(Integer.toString(i)); + } + + public void setBooleanValue(boolean b) { + setValue(Boolean.toString(b)); + } + + public void setHexValue(int i) { + setValue("0x" + Long.toString(((long) i) << 32 >>> 32, 16)); + } + + public void set(IConfigType type, T entry) { + setValue(type.configValue(entry)); + } + + public String getValue() { + return value; + } + + public String getValue(String defaultValue) { + setDefaultValue(defaultValue); + return value; + } + + public int getIntValue() { + return Integer.parseInt(getValue()); + } + + public int getIntValue(int defaultValue) { + try { + if (value != null) return getIntValue(); + } catch (NumberFormatException ignored) {} + + setIntValue(defaultValue); + return defaultValue; + } + + public boolean getBooleanValue() { + String value = getValue(); + if (value != null && (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"))) return true; + else if (value != null && (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no"))) return false; + + throw new NumberFormatException(qualifiedname + ".value=" + value); + } + + public boolean getBooleanValue(boolean defaultValue) { + try { + if (value != null) return getBooleanValue(); + } catch (NumberFormatException ignored) {} + + setBooleanValue(defaultValue); + return defaultValue; + } + + public int getHexValue() { + return (int) Long.parseLong(getValue().replace("0x", ""), 16); + } + + public int getHexValue(int defaultValue) { + try { + if (value != null) return getHexValue(); + } catch (NumberFormatException ignored) {} + + setHexValue(defaultValue); + return defaultValue; + } + + public T get(IConfigType type) { + try { + return type.valueOf(getValue()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public T get(IConfigType type, T defaultValue) { + try { + if (value != null) return get(type); + } catch (Exception ignored) {} + + set(type, defaultValue); + return defaultValue; + } + + public void save(PrintWriter writer, int tabs, String bracequalifier, boolean first) { + String vname; + if (qualifiedname.contains(".") && bracequalifier.length() > 0) + vname = qualifiedname.substring(bracequalifier.length() + 1); + else vname = qualifiedname; + + if (newline && !first) ConfigFile.writeLine(writer, "", tabs); + + writeComment(writer, tabs); + if (value != null) ConfigFile.writeLine(writer, vname + "=" + value, tabs); + + if (!hasChildTags()) return; + + if (brace) { + if (value == null) ConfigFile.writeLine(writer, vname, tabs); + ConfigFile.writeLine(writer, "{", tabs); + saveTagTree(writer, tabs + 1, qualifiedname); + ConfigFile.writeLine(writer, "}", tabs); + } else { + saveTagTree(writer, tabs, bracequalifier); + } + } + + @Override + public ConfigTag setComment(String comment) { + super.setComment(comment); + return this; + } + + @Override + public ConfigTag setSortMode(int mode) { + super.setSortMode(mode); + return this; + } + + public ConfigTag setNewLine(boolean b) { + newline = b; + saveConfig(); + return this; + } + + public ConfigTag useBraces() { + brace = true; + if (parent.newlinemode == 1) newline = true; + + saveConfig(); + return this; + } + + public ConfigTag setPosition(int pos) { + position = pos; + saveConfig(); + return this; + } + + public boolean containsTag(String tagname) { + return getTag(tagname, false) != null; + } + + public int getId(String name, int defaultValue) { + return getTag(name).getIntValue(defaultValue); + } + + public int getId(String name) { + int ret = getId(name, IDBase); + IDBase = ret + 1; + return ret; + } + + public int getAcheivementId(String name, int defaultValue) { + return getTag(name).getIntValue(defaultValue); + } + + public ConfigTag setBaseID(int i) { + IDBase = i; + return this; + } + + public ConfigTagParent parent; + public String name; + public String qualifiedname; + public String value; + public boolean brace; + public boolean newline; + public int position = Integer.MAX_VALUE; + + private int IDBase; +} diff --git a/src/main/java/codechicken/lib/config/ConfigTagParent.java b/src/main/java/codechicken/lib/config/ConfigTagParent.java new file mode 100644 index 0000000..8d000b2 --- /dev/null +++ b/src/main/java/codechicken/lib/config/ConfigTagParent.java @@ -0,0 +1,220 @@ +package codechicken.lib.config; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeMap; + +public abstract class ConfigTagParent { + + public static class TagOrderComparator implements Comparator { + + int sortMode; + + public TagOrderComparator(int sortMode) { + this.sortMode = sortMode; + } + + public int compare(ConfigTag o1, ConfigTag o2) { + if (o1.position != o2.position) return Integer.compare(o1.position, o2.position); + if (o1.brace != o2.brace) return o1.brace ? 1 : -1; // braced one goes after + + if (sortMode == 1) { + if (Objects.equals(o1.value, o2.value)) return 0; + if (o1.value == null) return 1; + if (o2.value == null) return -1; + return o1.value.compareTo(o2.value); + } else { + return o1.name.compareTo(o2.name); + } + } + + } + + /** + * The immediate child tags of this tag + */ + private final TreeMap childtags = new TreeMap<>(); + /** + * A map of all tags in the tree, meaning all tags in this tag and all tags in child tags (recursively). Allows for + * quick access to any tag in the tree, without the need to traverse the tree part by part. + */ + private final Map allTagsInTree = new HashMap<>(); + public String comment; + /** + * 0 = name, 1 = value + */ + public int sortMode = 0; + /** + * The mode for determining when child tags should leave a blank line between them and the one above 0 = never, 1 = + * when braced, 2 = always + */ + public int newlinemode = 1; + + public abstract void saveConfig(); + + public abstract String getNameQualifier(); + + public ConfigTagParent setComment(String comment) { + this.comment = comment; + saveConfig(); + return this; + } + + public ConfigTagParent setSortMode(int mode) { + sortMode = mode; + saveConfig(); + return this; + } + + public ConfigTagParent setNewLineMode(int mode) { + newlinemode = mode; + for (Entry entry : childtags.entrySet()) { + ConfigTag tag = entry.getValue(); + if (newlinemode == 0) tag.newline = false; + else if (newlinemode == 1) tag.newline = tag.brace; + else if (newlinemode == 2) tag.newline = true; + } + saveConfig(); + return this; + } + + public Map childTagMap() { + return childtags; + } + + public boolean hasChildTags() { + return !childtags.isEmpty(); + } + + public boolean containsTag(String tagname) { + return allTagsInTree.get(tagname) != null; + } + + public ConfigTag getNewTag(String tagname) { + return new ConfigTag(this, tagname); + } + + public ConfigTag getTag(String tagname, boolean create) { + ConfigTag tag = allTagsInTree.get(tagname); + if (tag == null && create) return getTagRecursive(tagname, create); + return tag; + } + + public ConfigTag getTagRecursive(String tagname, boolean create) { + int dotpos = tagname.indexOf("."); + String basetagname = dotpos == -1 ? tagname : tagname.substring(0, dotpos); + ConfigTag basetag = childtags.get(basetagname); + if (basetag == null) { + if (!create) return null; + + basetag = getNewTag(basetagname); + saveConfig(); + } + if (dotpos == -1) return basetag; + + return basetag.getTag(tagname.substring(dotpos + 1), create); + } + + public ConfigTag getTag(String tagname) { + return getTag(tagname, true); + } + + public boolean removeTag(String tagname) { + removeChildFromFlatMap(tagname); + + ConfigTag tag = getTag(tagname, false); + if (tag == null) return false; + + int dotpos = tagname.lastIndexOf("."); + String lastpart = dotpos == -1 ? tagname : tagname.substring(dotpos + 1, tagname.length()); + if (tag.parent != null) { + boolean ret = tag.parent.childtags.remove(lastpart) != null; + if (ret) saveConfig(); + return ret; + } + + return false; + } + + public void addChild(ConfigTag tag) { + childtags.put(tag.name, tag); + + addChildToFlatMap(tag.name, tag); + } + + private void addChildToFlatMap(String path, ConfigTag tag) { + allTagsInTree.put(path, tag); + + // Stupid cast but these two garbage classes are interlinked anyway + if ((this instanceof ConfigTag t) && t.parent != null) t.parent.addChildToFlatMap(t.name + "." + path, tag); + } + + private void removeChildFromFlatMap(String path) { + allTagsInTree.remove(path); + + // Stupid cast but these two garbage classes are interlinked anyway + if ((this instanceof ConfigTag t) && t.parent != null) t.parent.removeChildFromFlatMap(t.name + "." + path); + } + + public ArrayList getSortedTagList() { + ArrayList taglist = new ArrayList(childtags.size()); + for (Entry tag : childtags.entrySet()) taglist.add((T) tag.getValue()); + + Collections.sort(taglist, new TagOrderComparator(sortMode)); + return taglist; + } + + public void loadChildren(BufferedReader reader) { + String comment = ""; + String bracequalifier = ""; + try { + while (true) { + String line = ConfigFile.readLine(reader); + if (line == null) break; + if (line.startsWith("#")) { + if (comment.equals("")) comment = line.substring(1); + else comment = comment + "\n" + line.substring(1); + } else if (line.contains("=")) { + String qualifiedname = line.substring(0, line.indexOf("=")); + getTag(qualifiedname).onLoaded().setComment(comment) + .setValue(line.substring(line.indexOf("=") + 1)); + comment = ""; + bracequalifier = qualifiedname; + } else if (line.equals("{")) { + getTag(bracequalifier).setComment(comment).useBraces().loadChildren(reader); + comment = ""; + bracequalifier = ""; + } else if (line.equals("}")) { + break; + } else { + bracequalifier = line; + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void saveTagTree(PrintWriter writer, int tabs, String bracequalifier) { + boolean first = true; + for (ConfigTag tag : getSortedTagList()) { + tag.save(writer, tabs, bracequalifier, first); + first = false; + } + } + + public void writeComment(PrintWriter writer, int tabs) { + if (comment != null && !comment.equals("")) { + String[] comments = comment.split("\n"); + for (int i = 0; i < comments.length; i++) ConfigFile.writeLine(writer, "#" + comments[i], tabs); + } + } +} diff --git a/src/main/java/codechicken/lib/config/DefaultingConfigFile.java b/src/main/java/codechicken/lib/config/DefaultingConfigFile.java new file mode 100644 index 0000000..237b7e5 --- /dev/null +++ b/src/main/java/codechicken/lib/config/DefaultingConfigFile.java @@ -0,0 +1,16 @@ +package codechicken.lib.config; + +import java.io.File; + +public class DefaultingConfigFile extends ConfigFile { + + public DefaultingConfigFile(File file) { + super(); + if (file.exists()) load(file); + } + + @Override + public void saveConfig() { + if (file != null) super.saveConfig(); + } +} diff --git a/src/main/java/codechicken/lib/config/SimpleProperties.java b/src/main/java/codechicken/lib/config/SimpleProperties.java new file mode 100644 index 0000000..4ec65e0 --- /dev/null +++ b/src/main/java/codechicken/lib/config/SimpleProperties.java @@ -0,0 +1,125 @@ +package codechicken.lib.config; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map.Entry; + +public class SimpleProperties { + + public HashMap propertyMap = new HashMap(); + public File propertyFile; + public boolean saveOnChange = false; + public String encoding; + + private boolean loading = false; + + public SimpleProperties(File file, boolean saveOnChange, String encoding) { + propertyFile = file; + this.saveOnChange = saveOnChange; + this.encoding = encoding; + } + + public SimpleProperties(File file, boolean saveOnChange) { + this(file, saveOnChange, Charset.defaultCharset().name()); + } + + public SimpleProperties(File file) { + this(file, true); + } + + public void load() { + clear(); + loading = true; + + try { + BufferedReader reader = new BufferedReader( + new InputStreamReader(new FileInputStream(propertyFile), encoding)); + while (true) { + String read = reader.readLine(); + if (read == null) break; + + int equalIndex = read.indexOf('='); + if (equalIndex == -1) continue; + + setProperty(read.substring(0, equalIndex), read.substring(equalIndex + 1)); + } + reader.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + loading = false; + } + + public void save() { + try { + PrintStream writer = new PrintStream(propertyFile); + + for (Entry entry : propertyMap.entrySet()) { + writer.println(entry.getKey() + "=" + entry.getValue()); + } + + writer.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void clear() { + propertyMap.clear(); + } + + public boolean hasProperty(String key) { + return propertyMap.containsKey(key); + } + + public void removeProperty(String key) { + if (propertyMap.remove(key) != null && saveOnChange && !loading) save(); + } + + public void setProperty(String key, int value) { + setProperty(key, Integer.toString(value)); + } + + public void setProperty(String key, boolean value) { + setProperty(key, Boolean.toString(value)); + } + + public void setProperty(String key, String value) { + propertyMap.put(key, value); + if (saveOnChange && !loading) save(); + } + + public int getProperty(String property, int defaultvalue) { + try { + return Integer.parseInt(getProperty(property, Integer.toString(defaultvalue))); + } catch (NumberFormatException nfe) { + return defaultvalue; + } + } + + public boolean getProperty(String property, boolean defaultvalue) { + try { + return Boolean.parseBoolean(getProperty(property, Boolean.toString(defaultvalue))); + } catch (NumberFormatException nfe) { + return defaultvalue; + } + } + + public String getProperty(String property, String defaultvalue) { + String value = propertyMap.get(property); + if (value == null) { + setProperty(property, defaultvalue); + return defaultvalue; + } + return value; + } + + public String getProperty(String property) { + return propertyMap.get(property); + } +} diff --git a/src/main/java/codechicken/lib/data/MCDataInput.java b/src/main/java/codechicken/lib/data/MCDataInput.java new file mode 100644 index 0000000..df31842 --- /dev/null +++ b/src/main/java/codechicken/lib/data/MCDataInput.java @@ -0,0 +1,46 @@ +package codechicken.lib.data; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; + +import codechicken.lib.vec.BlockCoord; + +public interface MCDataInput { + + public long readLong(); + + public int readInt(); + + public short readShort(); + + public int readUShort(); + + public byte readByte(); + + public short readUByte(); + + public double readDouble(); + + public float readFloat(); + + public boolean readBoolean(); + + public char readChar(); + + public int readVarShort(); + + public int readVarInt(); + + public byte[] readByteArray(int length); + + public String readString(); + + public BlockCoord readCoord(); + + public NBTTagCompound readNBTTagCompound(); + + public ItemStack readItemStack(); + + public FluidStack readFluidStack(); +} diff --git a/src/main/java/codechicken/lib/data/MCDataInputStream.java b/src/main/java/codechicken/lib/data/MCDataInputStream.java new file mode 100644 index 0000000..210a36c --- /dev/null +++ b/src/main/java/codechicken/lib/data/MCDataInputStream.java @@ -0,0 +1,17 @@ +package codechicken.lib.data; + +import java.io.InputStream; + +public class MCDataInputStream extends InputStream { + + private MCDataInput in; + + public MCDataInputStream(MCDataInput in) { + this.in = in; + } + + @Override + public int read() { + return in.readByte() & 0xFF; + } +} diff --git a/src/main/java/codechicken/lib/data/MCDataOutput.java b/src/main/java/codechicken/lib/data/MCDataOutput.java new file mode 100644 index 0000000..63f8b17 --- /dev/null +++ b/src/main/java/codechicken/lib/data/MCDataOutput.java @@ -0,0 +1,44 @@ +package codechicken.lib.data; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; + +import codechicken.lib.vec.BlockCoord; + +public interface MCDataOutput { + + public MCDataOutput writeLong(long l); + + public MCDataOutput writeInt(int i); + + public MCDataOutput writeShort(int s); + + public MCDataOutput writeByte(int b); + + public MCDataOutput writeDouble(double d); + + public MCDataOutput writeFloat(float f); + + public MCDataOutput writeBoolean(boolean b); + + public MCDataOutput writeChar(char c); + + public MCDataOutput writeVarInt(int i); + + public MCDataOutput writeVarShort(int s); + + public MCDataOutput writeByteArray(byte[] array); + + public MCDataOutput writeString(String s); + + public MCDataOutput writeCoord(int x, int y, int z); + + public MCDataOutput writeCoord(BlockCoord coord); + + public MCDataOutput writeNBTTagCompound(NBTTagCompound tag); + + public MCDataOutput writeItemStack(ItemStack stack); + + public MCDataOutput writeFluidStack(FluidStack liquid); +} diff --git a/src/main/java/codechicken/lib/data/MCDataOutputStream.java b/src/main/java/codechicken/lib/data/MCDataOutputStream.java new file mode 100644 index 0000000..ec9cfd8 --- /dev/null +++ b/src/main/java/codechicken/lib/data/MCDataOutputStream.java @@ -0,0 +1,17 @@ +package codechicken.lib.data; + +import java.io.OutputStream; + +public class MCDataOutputStream extends OutputStream { + + private MCDataOutput out; + + public MCDataOutputStream(MCDataOutput out) { + this.out = out; + } + + @Override + public void write(int b) { + out.writeByte(b); + } +} diff --git a/src/main/java/codechicken/lib/data/MCDataOutputWrapper.java b/src/main/java/codechicken/lib/data/MCDataOutputWrapper.java new file mode 100644 index 0000000..baea780 --- /dev/null +++ b/src/main/java/codechicken/lib/data/MCDataOutputWrapper.java @@ -0,0 +1,240 @@ +package codechicken.lib.data; + +import java.io.DataOutput; +import java.io.IOException; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; + +import org.apache.commons.lang3.Validate; + +import com.google.common.base.Charsets; + +import codechicken.lib.vec.BlockCoord; +import cpw.mods.fml.common.network.ByteBufUtils; + +public class MCDataOutputWrapper implements MCDataOutput { + + /** + * Mimics ByteBufUtils Write an integer using variable length encoding. + * + * @param to The buffer to write to + * @param i The integer to write + */ + public static void writeVarInt(DataOutput to, int i) throws IOException { + while ((i & 0x80) != 0) { + to.writeByte(i & 0x7F | 0x80); + i >>>= 7; + } + + to.writeByte(i); + } + + /** + * Mimics ByteBufUtils Write an extended short using a short and a byte if necessary + * + * @param to The buffer to write to + * @param s The short to write, less than 0x7FFFFF + */ + public static void writeVarShort(DataOutput to, int s) throws IOException { + int low = s & 0x7FFF; + int high = (s & 0x7F8000) >> 15; + if (high != 0) low |= 0x8000; + to.writeShort(low); + if (high != 0) to.writeByte(high); + } + + /** + * Mimics ByteBufUtils Write a String with UTF8 byte encoding to the buffer. It is encoded as [] + * + * @param to the data output to write to + * @param string The string to write + */ + public static void writeUTF8String(DataOutput to, String string) throws IOException { + byte[] utf8Bytes = string.getBytes(Charsets.UTF_8); + Validate.isTrue( + ByteBufUtils.varIntByteCount(utf8Bytes.length) < 3, + "The string is too long for this encoding."); + writeVarInt(to, utf8Bytes.length); + to.write(utf8Bytes); + } + + public DataOutput dataout; + + public MCDataOutputWrapper(DataOutput out) { + dataout = out; + } + + public MCDataOutputWrapper writeBoolean(boolean b) { + try { + dataout.writeBoolean(b); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeByte(int b) { + try { + dataout.writeByte(b); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeShort(int s) { + try { + dataout.writeShort(s); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeInt(int i) { + try { + dataout.writeInt(i); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeFloat(float f) { + try { + dataout.writeFloat(f); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeDouble(double d) { + try { + dataout.writeDouble(d); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeLong(long l) { + try { + dataout.writeLong(l); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + @Override + public MCDataOutputWrapper writeChar(char c) { + try { + dataout.writeChar(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + @Override + public MCDataOutput writeVarInt(int i) { + try { + writeVarInt(dataout, i); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + @Override + public MCDataOutput writeVarShort(int s) { + try { + writeVarShort(dataout, s); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeByteArray(byte[] barray) { + try { + dataout.write(barray); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeCoord(int x, int y, int z) { + writeInt(x); + writeInt(y); + writeInt(z); + return this; + } + + public MCDataOutputWrapper writeCoord(BlockCoord coord) { + writeInt(coord.x); + writeInt(coord.y); + writeInt(coord.z); + return this; + } + + public MCDataOutputWrapper writeString(String s) { + try { + writeUTF8String(dataout, s); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeItemStack(ItemStack stack) { + writeItemStack(stack, false); + return this; + } + + public MCDataOutputWrapper writeItemStack(ItemStack stack, boolean large) { + if (stack == null) { + writeInt(-1); + } else { + writeInt(Item.getIdFromItem(stack.getItem())); + if (large) writeInt(stack.stackSize); + else writeByte(stack.stackSize); + writeShort(stack.getItemDamage()); + writeNBTTagCompound(stack.stackTagCompound); + } + return this; + } + + public MCDataOutputWrapper writeNBTTagCompound(NBTTagCompound compound) { + try { + if (compound == null) { + writeShort(-1); + } else { + byte[] bytes = CompressedStreamTools.compress(compound); + writeShort((short) bytes.length); + writeByteArray(bytes); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeFluidStack(FluidStack fluid) { + if (fluid == null) { + writeShort(-1); + } else { + writeShort(fluid.getFluidID()); + writeVarInt(fluid.amount); + writeNBTTagCompound(fluid.tag); + } + return this; + } +} diff --git a/src/main/java/codechicken/lib/gui/Canvas9Seg.java b/src/main/java/codechicken/lib/gui/Canvas9Seg.java new file mode 100644 index 0000000..b468b42 --- /dev/null +++ b/src/main/java/codechicken/lib/gui/Canvas9Seg.java @@ -0,0 +1,81 @@ +package codechicken.lib.gui; + +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.render.TextureDataHolder; +import codechicken.lib.render.TextureUtils; + +public class Canvas9Seg { + + public final ResourceLocation tex; + public float[] seg_u = new float[4]; + public float[] seg_v = new float[4]; + public int[] seg_w = new int[3]; + public int[] seg_h = new int[3]; + + public Canvas9Seg(ResourceLocation tex) { + this.tex = tex; + load(); + } + + private int[] readMarkers(TextureDataHolder data, int stride, int size) { + int[] markers = new int[4]; + + int marker = 1; + int prev_col = data.data[0]; + for (int i = 1; i < size; i++) { + if (data.data[i * stride] != prev_col) { + markers[marker] = i; + prev_col = data.data[i * stride]; + if (++marker == 4) break; + } + } + + markers[0] += 1; + markers[3] -= 1; + return markers; + } + + private void parseMarkers(TextureDataHolder data, int stride, int size, int[] sizes, float[] texcoords) { + int[] markers = readMarkers(data, stride, size); + for (int i = 0; i < 4; i++) { + texcoords[i] = markers[i] / (float) size; + if (i > 0) sizes[i - 1] = markers[i] - markers[i - 1]; + } + } + + private void load() { + TextureDataHolder data = TextureUtils.loadTexture(tex); + parseMarkers(data, 1, data.width, seg_w, seg_u); + parseMarkers(data, data.width, data.height, seg_h, seg_v); + } + + private void drawSeg(int[] sw, int[] sh, int seg) { + Tessellator t = Tessellator.instance; + int u = seg % 3; + int v = seg / 3; + t.addVertexWithUV(sw[u], sh[v], 0, seg_u[u], seg_v[v]); + t.addVertexWithUV(sw[u], sh[v + 1], 0, seg_u[u], seg_v[v + 1]); + t.addVertexWithUV(sw[u + 1], sh[v + 1], 0, seg_u[u + 1], seg_v[v + 1]); + t.addVertexWithUV(sw[u + 1], sh[v], 0, seg_u[u + 1], seg_v[v]); + } + + public void draw(CCRenderState state, int x, int y, int w, int h) { + CCRenderState.changeTexture(tex); + state.resetInstance(); + state.startDrawingInstance(); + + int[] sw = new int[] { x, x + seg_w[0], x + w - seg_w[2], x + w }; + int[] sh = new int[] { y, y + seg_h[0], y + h - seg_h[2], y + h }; + + for (int seg = 0; seg < 9; seg++) drawSeg(sw, sh, seg); + + state.drawInstance(); + } + + public void draw(int x, int y, int w, int h) { + draw(CCRenderState.instance(), x, y, w, h); + } +} diff --git a/src/main/java/codechicken/lib/gui/GuiDraw.java b/src/main/java/codechicken/lib/gui/GuiDraw.java new file mode 100644 index 0000000..380e6d9 --- /dev/null +++ b/src/main/java/codechicken/lib/gui/GuiDraw.java @@ -0,0 +1,237 @@ +package codechicken.lib.gui; + +import java.awt.Dimension; +import java.awt.Point; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.util.ResourceLocation; + +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.render.CCRenderState; + +public class GuiDraw { + + public static class GuiHook extends Gui { + + public void setZLevel(float f) { + zLevel = f; + } + + public float getZLevel() { + return zLevel; + } + + public void incZLevel(float f) { + zLevel += f; + } + + @Override + public void drawGradientRect(int par1, int par2, int par3, int par4, int par5, int par6) { + super.drawGradientRect(par1, par2, par3, par4, par5, par6); + } + } + + public static final GuiHook gui = new GuiHook(); + public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; + public static TextureManager renderEngine = Minecraft.getMinecraft().renderEngine; + + public static void drawRect(int x, int y, int w, int h, int colour) { + drawGradientRect(x, y, w, h, colour, colour); + } + + public static void drawGradientRect(int x, int y, int w, int h, int colour1, int colour2) { + gui.drawGradientRect(x, y, x + w, y + h, colour1, colour2); + } + + public static void drawTexturedModalRect(int x, int y, int tx, int ty, int w, int h) { + gui.drawTexturedModalRect(x, y, tx, ty, w, h); + } + + public static void drawString(String text, int x, int y, int colour, boolean shadow) { + if (shadow) fontRenderer.drawStringWithShadow(text, x, y, colour); + else fontRenderer.drawString(text, x, y, colour); + } + + public static void drawString(String text, int x, int y, int colour) { + drawString(text, x, y, colour, true); + } + + public static void drawStringC(String text, int x, int y, int w, int h, int colour, boolean shadow) { + drawString(text, x + (w - getStringWidth(text)) / 2, y + (h - 8) / 2, colour, shadow); + } + + public static void drawStringC(String text, int x, int y, int w, int h, int colour) { + drawStringC(text, x, y, w, h, colour, true); + } + + public static void drawStringC(String text, int x, int y, int colour, boolean shadow) { + drawString(text, x - getStringWidth(text) / 2, y, colour, shadow); + } + + public static void drawStringC(String text, int x, int y, int colour) { + drawStringC(text, x, y, colour, true); + } + + public static void drawStringR(String text, int x, int y, int colour, boolean shadow) { + drawString(text, x - getStringWidth(text), y, colour, shadow); + } + + public static void drawStringR(String text, int x, int y, int colour) { + drawStringR(text, x, y, colour, true); + } + + public static int getStringWidth(String s) { + return fontRenderer.getStringWidth(s); + } + + public static Dimension displaySize() { + Minecraft mc = Minecraft.getMinecraft(); + ScaledResolution res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); + return new Dimension(res.getScaledWidth(), res.getScaledHeight()); + } + + public static Dimension displayRes() { + Minecraft mc = Minecraft.getMinecraft(); + return new Dimension(mc.displayWidth, mc.displayHeight); + } + + public static Point getMousePosition(int eventX, int eventY) { + Dimension size = displaySize(); + Dimension res = displayRes(); + return new Point(eventX * size.width / res.width, size.height - eventY * size.height / res.height - 1); + } + + public static Point getMousePosition() { + return getMousePosition(Mouse.getX(), Mouse.getY()); + } + + public static void changeTexture(String s) { + CCRenderState.changeTexture(s); + } + + public static void changeTexture(ResourceLocation r) { + CCRenderState.changeTexture(r); + } + + public static void drawTip(int x, int y, String text) { + drawMultilineTip(x, y, Arrays.asList(text)); + } + + /** + * Append a string in the tooltip list with TOOLTIP_LINESPACE to have a small gap between it and the next line + */ + public static final String TOOLTIP_LINESPACE = "\u00A7h"; + /** + * Have a string in the tooltip list with TOOLTIP_HANDLER + getTipLineId(handler) for a custom handler + */ + public static final String TOOLTIP_HANDLER = "\u00A7x"; + + private static List tipLineHandlers = new ArrayList(); + + public static interface ITooltipLineHandler { + + public Dimension getSize(); + + public void draw(int x, int y); + } + + public static int getTipLineId(ITooltipLineHandler handler) { + tipLineHandlers.add(handler); + return tipLineHandlers.size() - 1; + } + + public static ITooltipLineHandler getTipLine(String line) { + if (!line.startsWith(TOOLTIP_HANDLER)) return null; + return tipLineHandlers.get(Integer.parseInt(line.substring(2))); + } + + public static void drawMultilineTip(int x, int y, List list) { + drawMultilineTip(fontRenderer, x, y, list); + } + + public static void drawMultilineTip(FontRenderer font, int x, int y, List list) { + drawMultilineTip(font, x, y, list, 0xf0100010, 0xf0100010, 0x505000ff, 0x5028007F); + } + + public static void drawMultilineTip(FontRenderer font, int x, int y, List list, int bgStart, int bgEnd, + int borderStart, int borderEnd) { + if (list.isEmpty()) return; + + GL11.glDisable(GL12.GL_RESCALE_NORMAL); + GL11.glDisable(GL11.GL_DEPTH_TEST); + RenderHelper.disableStandardItemLighting(); + + int w = 0; + int h = -2; + for (int i = 0; i < list.size(); i++) { + String s = list.get(i); + ITooltipLineHandler line = getTipLine(s); + Dimension d = line != null ? line.getSize() + : new Dimension( + font.getStringWidth(s), + list.get(i).endsWith(TOOLTIP_LINESPACE) && i + 1 < list.size() ? 12 : 10); + w = Math.max(w, d.width); + h += d.height; + } + + if (x < 8) x = 8; + else if (x > displaySize().width - w - 8) { + x -= 24 + w; // flip side of cursor + if (x < 8) x = 8; + } + y = (int) MathHelper.clip(y, 8, displaySize().height - 8 - h); + + gui.incZLevel(300); + drawTooltipBox(x - 4, y - 4, w + 7, h + 7, bgStart, bgEnd, borderStart, borderEnd); + for (String s : list) { + ITooltipLineHandler line = getTipLine(s); + if (line != null) { + line.draw(x, y); + y += line.getSize().height; + } else { + font.drawStringWithShadow(s, x, y, -1); + y += s.endsWith(TOOLTIP_LINESPACE) ? 12 : 10; + } + } + + tipLineHandlers.clear(); + gui.incZLevel(-300); + + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + RenderHelper.enableGUIStandardItemLighting(); + } + + public static void drawTooltipBox(int x, int y, int w, int h) { + drawTooltipBox(x, y, w, h, 0xf0100010, 0xf0100010, 0x505000ff, 0x5028007F); + } + + public static void drawTooltipBox(int x, int y, int w, int h, int bgStart, int bgEnd, int borderStart, + int borderEnd) { + // spotless:off + // draw background + drawGradientRect(x + 1, y, w - 1, 1, bgStart, bgStart); // top + drawGradientRect(x + 1, y + h, w - 1, 1, bgEnd, bgEnd); // bottom + drawGradientRect(x + 1, y + 1, w - 1, h - 1, bgStart, bgEnd); // center + drawGradientRect(x, y + 1, 1, h - 1, bgStart, bgEnd); // left + drawGradientRect(x + w, y + 1, 1, h - 1, bgStart, bgEnd); // right + // draw inner border + drawGradientRect(x + 1, y + 2, 1, h - 3, borderStart, borderEnd); // left + drawGradientRect(x + w - 1, y + 2, 1, h - 3, borderStart, borderEnd); // right + drawGradientRect(x + 1, y + 1, w - 1, 1, borderStart, borderStart); // top + drawGradientRect(x + 1, y + h - 1, w - 1, 1, borderEnd, borderEnd); // bottom + // spotless:on + } +} diff --git a/src/main/java/codechicken/lib/inventory/ContainerExtended.java b/src/main/java/codechicken/lib/inventory/ContainerExtended.java new file mode 100644 index 0000000..d8448a1 --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/ContainerExtended.java @@ -0,0 +1,274 @@ +package codechicken.lib.inventory; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.ICrafting; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.network.play.INetHandlerPlayClient; +import net.minecraft.network.play.INetHandlerPlayServer; + +import org.apache.logging.log4j.LogManager; + +import codechicken.lib.packet.PacketCustom; +import codechicken.lib.packet.PacketCustom.IClientPacketHandler; +import codechicken.lib.packet.PacketCustom.IServerPacketHandler; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.relauncher.Side; + +/** + * Clean container implementation with a few extra features. Easy shift-click handling. Hooks for slot clicks, large + * stack sizes and some networking. + */ +public abstract class ContainerExtended extends Container implements ICrafting { + + private static final String netChannel = "CCL:Container"; + private static int nextNetworkID = 0; + + static { + PacketCustom.assignHandler(netChannel, new IClientPacketHandler() { + + @Override + public void handlePacket(PacketCustom packet, Minecraft mc, INetHandlerPlayClient handler) { + Container cont = mc.thePlayer.openContainer; + if (!(cont instanceof ContainerExtended)) return; + + ContainerExtended c = (ContainerExtended) cont; + if (packet.getType() == 1) c.netID = packet.readInt(); + else if (c.netID == packet.readInt()) c.handleClientPacket(packet); + } + }); + PacketCustom.assignHandler(netChannel, new IServerPacketHandler() { + + @Override + public void handlePacket(PacketCustom packet, EntityPlayerMP sender, INetHandlerPlayServer handler) { + Container cont = sender.openContainer; + if (!(cont instanceof ContainerExtended)) return; + + ContainerExtended c = (ContainerExtended) cont; + if (c.netID == packet.readInt()) c.handleServerPacket(packet); + } + }); + } + + public LinkedList playerCrafters = new LinkedList(); + private int netID; + + public ContainerExtended() { + crafters.add(this); + if (FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) netID = ++nextNetworkID; + } + + @Override + public void addCraftingToCrafters(ICrafting icrafting) { + if (icrafting instanceof EntityPlayerMP) { + playerCrafters.add((EntityPlayerMP) icrafting); + sendNetID((EntityPlayerMP) icrafting); + sendContainerAndContentsToPlayer(this, getInventory(), Arrays.asList((EntityPlayerMP) icrafting)); + detectAndSendChanges(); + } else super.addCraftingToCrafters(icrafting); + } + + private void sendNetID(EntityPlayerMP player) { + if (netID == 0) LogManager.getLogger("CodeChickenLib").error("Player added to container with 0 network ID"); + else new PacketCustom(netChannel, 1).writeInt(netID).sendToPlayer(player); + } + + @Override + public void removeCraftingFromCrafters(ICrafting icrafting) { + if (icrafting instanceof EntityPlayerMP) playerCrafters.remove(icrafting); + else super.removeCraftingFromCrafters(icrafting); + } + + @Override + public void sendContainerAndContentsToPlayer(Container container, List list) { + sendContainerAndContentsToPlayer(container, list, playerCrafters); + } + + public void sendContainerAndContentsToPlayer(Container container, List list, + List playerCrafters) { + LinkedList largeStacks = new LinkedList(); + for (int i = 0; i < list.size(); i++) { + ItemStack stack = list.get(i); + if (stack != null && stack.stackSize > Byte.MAX_VALUE) { + list.set(i, null); + largeStacks.add(stack); + } else largeStacks.add(null); + } + + for (EntityPlayerMP player : playerCrafters) player.sendContainerAndContentsToPlayer(container, list); + + for (int i = 0; i < largeStacks.size(); i++) { + ItemStack stack = largeStacks.get(i); + if (stack != null) sendLargeStack(stack, i, playerCrafters); + } + } + + public void sendLargeStack(ItemStack stack, int slot, List players) {} + + @Override + public void sendProgressBarUpdate(Container container, int i, int j) { + for (EntityPlayerMP player : playerCrafters) player.sendProgressBarUpdate(container, i, j); + } + + @Override + public void sendSlotContents(Container container, int slot, ItemStack stack) { + if (stack != null && stack.stackSize > Byte.MAX_VALUE) sendLargeStack(stack, slot, playerCrafters); + else for (EntityPlayerMP player : playerCrafters) player.sendSlotContents(container, slot, stack); + } + + @Override + public ItemStack slotClick(int par1, int par2, int par3, EntityPlayer player) { + if (par1 >= 0 && par1 < inventorySlots.size()) { + Slot slot = getSlot(par1); + if (slot instanceof SlotHandleClicks) return ((SlotHandleClicks) slot).slotClick(this, player, par2, par3); + } + return super.slotClick(par1, par2, par3, player); + } + + @Override + public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int slotIndex) { + ItemStack transferredStack = null; + Slot slot = (Slot) inventorySlots.get(slotIndex); + + if (slot != null && slot.getHasStack()) { + ItemStack stack = slot.getStack(); + transferredStack = stack.copy(); + + if (!doMergeStackAreas(slotIndex, stack)) return null; + + if (stack.stackSize == 0) slot.putStack(null); + else slot.onSlotChanged(); + } + + return transferredStack; + } + + @Override + public boolean mergeItemStack(ItemStack stack, int startIndex, int endIndex, boolean reverse) { + boolean merged = false; + int slotIndex = reverse ? endIndex - 1 : startIndex; + + if (stack == null) return false; + + if (stack.isStackable()) // search for stacks to increase + { + while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) { + Slot slot = (Slot) inventorySlots.get(slotIndex); + ItemStack slotStack = slot.getStack(); + + if (slotStack != null && slotStack.getItem() == stack.getItem() + && (!stack.getHasSubtypes() || stack.getItemDamage() == slotStack.getItemDamage()) + && ItemStack.areItemStackTagsEqual(stack, slotStack)) { + int totalStackSize = slotStack.stackSize + stack.stackSize; + int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit()); + if (totalStackSize <= maxStackSize) { + stack.stackSize = 0; + slotStack.stackSize = totalStackSize; + slot.onSlotChanged(); + merged = true; + } else if (slotStack.stackSize < maxStackSize) { + stack.stackSize -= maxStackSize - slotStack.stackSize; + slotStack.stackSize = maxStackSize; + slot.onSlotChanged(); + merged = true; + } + } + + slotIndex += reverse ? -1 : 1; + } + } + + if (stack.stackSize > 0) // normal transfer :) + { + slotIndex = reverse ? endIndex - 1 : startIndex; + + while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) { + Slot slot = (Slot) this.inventorySlots.get(slotIndex); + + if (!slot.getHasStack() && slot.isItemValid(stack)) { + int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit()); + if (stack.stackSize <= maxStackSize) { + slot.putStack(stack.copy()); + slot.onSlotChanged(); + stack.stackSize = 0; + merged = true; + } else { + slot.putStack(stack.splitStack(maxStackSize)); + slot.onSlotChanged(); + merged = true; + } + } + + slotIndex += reverse ? -1 : 1; + } + } + + return merged; + } + + /** + * Called when slotIndex is shift clicked on. Recommended implementation is to call mergeItemStack based on + * slotIndex + * + * @param stack The stack in the clicked slot + * @return True if one or more items were moved from this slots into other slots + */ + public boolean doMergeStackAreas(int slotIndex, ItemStack stack) { + return false; + } + + protected void bindPlayerInventory(InventoryPlayer inventoryPlayer) { + bindPlayerInventory(inventoryPlayer, 8, 84); + } + + protected void bindPlayerInventory(InventoryPlayer inventoryPlayer, int x, int y) { + for (int row = 0; row < 3; row++) for (int col = 0; col < 9; col++) + addSlotToContainer(new Slot(inventoryPlayer, col + row * 9 + 9, x + col * 18, y + row * 18)); + for (int slot = 0; slot < 9; slot++) addSlotToContainer(new Slot(inventoryPlayer, slot, x + slot * 18, y + 58)); + } + + @Override + public boolean canInteractWith(EntityPlayer var1) { + return true; + } + + public void sendContainerPacket(PacketCustom packet) { + for (EntityPlayerMP player : playerCrafters) packet.sendToPlayer(player); + } + + /** + * @param type An identifying number for the packet type between 2 and 0x79 inclusive. 1 is reserved for + * synchronising networkIDs + * @return A packet on the CCL inventory channel that will be recieved by this container on the other network side + */ + public PacketCustom getPacket(int type) { + if (netID == 0) + LogManager.getLogger("CodeChickenLib").error("Tried to get packet for container with 0 network ID"); + if (type == 1) throw new IllegalArgumentException( + "Packet type 1 is reserved for network synchronisation in ContainerExtended"); + + return new PacketCustom(netChannel, type).writeInt(netID); + } + + /** + * Handle a packet from the server obtained by getPacket. + */ + public void handleClientPacket(PacketCustom packet) {} + + /** + * Handle a packet from the client obtained by getPacket. + */ + public void handleServerPacket(PacketCustom packet) {} + + public void sendProgressBarUpdate(int barID, int value) { + for (ICrafting crafting : (List) crafters) crafting.sendProgressBarUpdate(this, barID, value); + } +} diff --git a/src/main/java/codechicken/lib/inventory/ContainerSynchronised.java b/src/main/java/codechicken/lib/inventory/ContainerSynchronised.java new file mode 100644 index 0000000..8439a3b --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/ContainerSynchronised.java @@ -0,0 +1,67 @@ +package codechicken.lib.inventory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.Container; +import net.minecraft.item.ItemStack; + +import codechicken.lib.packet.PacketCustom; + +public abstract class ContainerSynchronised extends ContainerExtended { + + private ArrayList syncVars = new ArrayList(); + + /** + * Create a packet to be used to send a synced variable update. Calls getPacket. Can be overriden to add extra + * identifying data or change the type (default 2) + */ + public PacketCustom createSyncPacket() { + return getPacket(2); + } + + @Override + public final void detectAndSendChanges() { + super.detectAndSendChanges(); + + for (int i = 0; i < syncVars.size(); i++) { + IContainerSyncVar var = syncVars.get(i); + if (var.changed()) { + PacketCustom packet = createSyncPacket(); + packet.writeByte(i); + var.writeChange(packet); + sendContainerPacket(packet); + var.reset(); + } + } + } + + @Override + public void sendContainerAndContentsToPlayer(Container container, List list, + List playerCrafters) { + super.sendContainerAndContentsToPlayer(container, list, playerCrafters); + for (int i = 0; i < syncVars.size(); i++) { + IContainerSyncVar var = syncVars.get(i); + PacketCustom packet = createSyncPacket(); + packet.writeByte(i); + var.writeChange(packet); + var.reset(); + for (EntityPlayerMP player : playerCrafters) packet.sendToPlayer(player); + } + } + + public void addSyncVar(IContainerSyncVar var) { + syncVars.add(var); + } + + @Override + public final void handleClientPacket(PacketCustom packet) { + syncVars.get(packet.readUByte()).readChange(packet); + } + + public List getSyncedVars() { + return Collections.unmodifiableList(syncVars); + } +} diff --git a/src/main/java/codechicken/lib/inventory/IContainerSyncVar.java b/src/main/java/codechicken/lib/inventory/IContainerSyncVar.java new file mode 100644 index 0000000..07b3e25 --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/IContainerSyncVar.java @@ -0,0 +1,14 @@ +package codechicken.lib.inventory; + +import codechicken.lib.packet.PacketCustom; + +public interface IContainerSyncVar { + + public boolean changed(); + + public void reset(); + + public void writeChange(PacketCustom packet); + + public void readChange(PacketCustom packet); +} diff --git a/src/main/java/codechicken/lib/inventory/IntegerSync.java b/src/main/java/codechicken/lib/inventory/IntegerSync.java new file mode 100644 index 0000000..9cde12e --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/IntegerSync.java @@ -0,0 +1,30 @@ +package codechicken.lib.inventory; + +import codechicken.lib.packet.PacketCustom; + +public abstract class IntegerSync implements IContainerSyncVar { + + public int c_value; + + @Override + public boolean changed() { + return getValue() != c_value; + } + + @Override + public void reset() { + c_value = getValue(); + } + + @Override + public void writeChange(PacketCustom packet) { + packet.writeInt(getValue()); + } + + @Override + public void readChange(PacketCustom packet) { + c_value = packet.readInt(); + } + + public abstract int getValue(); +} diff --git a/src/main/java/codechicken/lib/inventory/InventoryCopy.java b/src/main/java/codechicken/lib/inventory/InventoryCopy.java new file mode 100644 index 0000000..06e7b71 --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/InventoryCopy.java @@ -0,0 +1,103 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +/** + * Creates a copy of an IInventory for extended simulation + */ +public class InventoryCopy implements IInventory { + + public boolean[] accessible; + public ItemStack[] items; + public IInventory inv; + + public InventoryCopy(IInventory inv) { + items = new ItemStack[inv.getSizeInventory()]; + accessible = new boolean[inv.getSizeInventory()]; + this.inv = inv; + update(); + } + + public void update() { + for (int i = 0; i < items.length; i++) { + ItemStack stack = inv.getStackInSlot(i); + if (stack != null) items[i] = stack.copy(); + } + } + + public InventoryCopy open(InventoryRange access) { + int lslot = access.lastSlot(); + if (lslot > accessible.length) { + boolean[] l_accessible = new boolean[lslot]; + ItemStack[] l_items = new ItemStack[lslot]; + System.arraycopy(accessible, 0, l_accessible, 0, accessible.length); + System.arraycopy(items, 0, l_items, 0, items.length); + accessible = l_accessible; + items = l_items; + } + + for (int slot : access.slots) accessible[slot] = true; + return this; + } + + @Override + public int getSizeInventory() { + return items.length; + } + + @Override + public ItemStack getStackInSlot(int slot) { + return items[slot]; + } + + public ItemStack decrStackSize(int slot, int amount) { + return InventoryUtils.decrStackSize(this, slot, amount); + } + + @Override + public ItemStack getStackInSlotOnClosing(int slot) { + return InventoryUtils.getStackInSlotOnClosing(this, slot); + } + + @Override + public void setInventorySlotContents(int slot, ItemStack stack) { + items[slot] = stack; + markDirty(); + } + + @Override + public String getInventoryName() { + return "copy"; + } + + @Override + public boolean isUseableByPlayer(EntityPlayer player) { + return true; + } + + @Override + public void openInventory() {} + + @Override + public void closeInventory() {} + + @Override + public int getInventoryStackLimit() { + return 64; + } + + @Override + public void markDirty() {} + + @Override + public boolean isItemValidForSlot(int i, ItemStack itemstack) { + return inv.isItemValidForSlot(i, itemstack); + } + + @Override + public boolean hasCustomInventoryName() { + return true; + } +} diff --git a/src/main/java/codechicken/lib/inventory/InventoryNBT.java b/src/main/java/codechicken/lib/inventory/InventoryNBT.java new file mode 100644 index 0000000..b721741 --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/InventoryNBT.java @@ -0,0 +1,91 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; + +/** + * IInventory implementation which saves and loads from an NBT tag + */ +public class InventoryNBT implements IInventory { + + protected ItemStack[] items; + protected NBTTagCompound tag; + + public InventoryNBT(int size, NBTTagCompound tag) { + this.tag = tag; + items = new ItemStack[size]; + readNBT(); + } + + private void writeNBT() { + tag.setTag("items", InventoryUtils.writeItemStacksToTag(items, getInventoryStackLimit())); + } + + private void readNBT() { + if (tag.hasKey("items")) InventoryUtils.readItemStacksFromTag(items, tag.getTagList("items", 10)); + } + + @Override + public int getSizeInventory() { + return items.length; + } + + @Override + public ItemStack getStackInSlot(int slot) { + return items[slot]; + } + + @Override + public ItemStack decrStackSize(int slot, int amount) { + return InventoryUtils.decrStackSize(this, slot, amount); + } + + @Override + public ItemStack getStackInSlotOnClosing(int slot) { + return InventoryUtils.getStackInSlotOnClosing(this, slot); + } + + @Override + public void setInventorySlotContents(int slot, ItemStack stack) { + items[slot] = stack; + markDirty(); + } + + @Override + public String getInventoryName() { + return "NBT"; + } + + @Override + public int getInventoryStackLimit() { + return 64; + } + + @Override + public void markDirty() { + writeNBT(); + } + + @Override + public boolean isUseableByPlayer(EntityPlayer var1) { + return true; + } + + @Override + public void openInventory() {} + + @Override + public void closeInventory() {} + + @Override + public boolean isItemValidForSlot(int i, ItemStack itemstack) { + return true; + } + + @Override + public boolean hasCustomInventoryName() { + return true; + } +} diff --git a/src/main/java/codechicken/lib/inventory/InventoryRange.java b/src/main/java/codechicken/lib/inventory/InventoryRange.java new file mode 100644 index 0000000..dacb4f5 --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/InventoryRange.java @@ -0,0 +1,59 @@ +package codechicken.lib.inventory; + +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.ISidedInventory; +import net.minecraft.item.ItemStack; + +/** + * Inventory wrapper for unified ISided/IInventory access + */ +public class InventoryRange { + + public IInventory inv; + public int side; + public ISidedInventory sidedInv; + public int[] slots; + + public InventoryRange(IInventory inv, int side) { + this.inv = inv; + this.side = side; + if (inv instanceof ISidedInventory) { + sidedInv = (ISidedInventory) inv; + slots = sidedInv.getAccessibleSlotsFromSide(side); + } else { + slots = new int[inv.getSizeInventory()]; + for (int i = 0; i < slots.length; i++) slots[i] = i; + } + } + + public InventoryRange(IInventory inv) { + this(inv, 0); + } + + public InventoryRange(IInventory inv, int fslot, int size) { + this.inv = inv; + slots = new int[size]; + for (int i = 0; i < slots.length; i++) slots[i] = fslot + i; + } + + public InventoryRange(IInventory inv, InventoryRange access) { + this.inv = inv; + this.slots = access.slots; + this.side = access.side; + if (inv instanceof ISidedInventory) sidedInv = (ISidedInventory) inv; + } + + public boolean canInsertItem(int slot, ItemStack item) { + return sidedInv == null ? inv.isItemValidForSlot(slot, item) : sidedInv.canInsertItem(slot, item, side); + } + + public boolean canExtractItem(int slot, ItemStack item) { + return sidedInv == null ? inv.isItemValidForSlot(slot, item) : sidedInv.canExtractItem(slot, item, side); + } + + public int lastSlot() { + int last = 0; + for (int slot : slots) if (slot > last) last = slot; + return last; + } +} diff --git a/src/main/java/codechicken/lib/inventory/InventorySimple.java b/src/main/java/codechicken/lib/inventory/InventorySimple.java new file mode 100644 index 0000000..ffbd604 --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/InventorySimple.java @@ -0,0 +1,109 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +/** + * Simple IInventory implementation with an array of items, name and maximum stack size + */ +public class InventorySimple implements IInventory { + + public ItemStack[] items; + public int limit; + public String name; + + public InventorySimple(ItemStack[] items, int limit, String name) { + this.items = items; + this.limit = limit; + this.name = name; + } + + public InventorySimple(ItemStack[] items, String name) { + this(items, 64, name); + } + + public InventorySimple(ItemStack[] items, int limit) { + this(items, limit, "inv"); + } + + public InventorySimple(ItemStack[] items) { + this(items, 64, "inv"); + } + + public InventorySimple(int size, int limit, String name) { + this(new ItemStack[size], limit, name); + } + + public InventorySimple(int size, int limit) { + this(size, limit, "inv"); + } + + public InventorySimple(int size, String name) { + this(size, 64, name); + } + + public InventorySimple(int size) { + this(size, 64, "inv"); + } + + @Override + public int getSizeInventory() { + return items.length; + } + + @Override + public ItemStack getStackInSlot(int slot) { + return items[slot]; + } + + @Override + public ItemStack decrStackSize(int slot, int amount) { + return InventoryUtils.decrStackSize(this, slot, amount); + } + + @Override + public ItemStack getStackInSlotOnClosing(int slot) { + return InventoryUtils.getStackInSlotOnClosing(this, slot); + } + + @Override + public void setInventorySlotContents(int slot, ItemStack stack) { + items[slot] = stack; + markDirty(); + } + + @Override + public String getInventoryName() { + return name; + } + + @Override + public int getInventoryStackLimit() { + return limit; + } + + @Override + public boolean isUseableByPlayer(EntityPlayer var1) { + return true; + } + + @Override + public void openInventory() {} + + @Override + public void closeInventory() {} + + @Override + public boolean isItemValidForSlot(int i, ItemStack itemstack) { + return true; + } + + @Override + public boolean hasCustomInventoryName() { + return true; + } + + @Override + public void markDirty() {} +} diff --git a/src/main/java/codechicken/lib/inventory/InventoryUtils.java b/src/main/java/codechicken/lib/inventory/InventoryUtils.java new file mode 100644 index 0000000..fa78174 --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/InventoryUtils.java @@ -0,0 +1,323 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Items; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.InventoryLargeChest; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityChest; +import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; + +import com.google.common.base.Objects; + +import codechicken.lib.vec.Vector3; + +public class InventoryUtils { + + /** + * Constructor for ItemStack with tag + */ + public static ItemStack newItemStack(Item item, int size, int damage, NBTTagCompound tag) { + ItemStack stack = new ItemStack(item, size, damage); + stack.stackTagCompound = tag; + return stack; + } + + /** + * Gets the actual damage of an item without asking the Item + */ + public static int actualDamage(ItemStack stack) { + return Items.diamond.getDamage(stack); + } + + /** + * Static default implementation for IInventory method + */ + public static ItemStack decrStackSize(IInventory inv, int slot, int size) { + ItemStack item = inv.getStackInSlot(slot); + + if (item != null) { + if (item.stackSize <= size) { + inv.setInventorySlotContents(slot, null); + inv.markDirty(); + return item; + } + ItemStack itemstack1 = item.splitStack(size); + if (item.stackSize == 0) inv.setInventorySlotContents(slot, null); + else inv.setInventorySlotContents(slot, item); + + inv.markDirty(); + return itemstack1; + } + return null; + } + + /** + * Static default implementation for IInventory method + */ + public static ItemStack getStackInSlotOnClosing(IInventory inv, int slot) { + ItemStack stack = inv.getStackInSlot(slot); + inv.setInventorySlotContents(slot, null); + return stack; + } + + /** + * @return The quantity of items from addition that can be added to base + */ + public static int incrStackSize(ItemStack base, ItemStack addition) { + if (canStack(base, addition)) return incrStackSize(base, addition.stackSize); + + return 0; + } + + /** + * @return The quantity of items from addition that can be added to base + */ + public static int incrStackSize(ItemStack base, int addition) { + int totalSize = base.stackSize + addition; + + if (totalSize <= base.getMaxStackSize()) return addition; + else if (base.stackSize < base.getMaxStackSize()) return base.getMaxStackSize() - base.stackSize; + + return 0; + } + + /** + * NBT item saving function + */ + public static NBTTagList writeItemStacksToTag(ItemStack[] items) { + return writeItemStacksToTag(items, 64); + } + + /** + * NBT item saving function with support for stack sizes > 32K + */ + public static NBTTagList writeItemStacksToTag(ItemStack[] items, int maxQuantity) { + NBTTagList tagList = new NBTTagList(); + for (int i = 0; i < items.length; i++) { + if (items[i] != null) { + NBTTagCompound tag = new NBTTagCompound(); + tag.setShort("Slot", (short) i); + items[i].writeToNBT(tag); + + if (maxQuantity > Short.MAX_VALUE) tag.setInteger("Quantity", items[i].stackSize); + else if (maxQuantity > Byte.MAX_VALUE) tag.setShort("Quantity", (short) items[i].stackSize); + + tagList.appendTag(tag); + } + } + return tagList; + } + + /** + * NBT item loading function with support for stack sizes > 32K + */ + public static void readItemStacksFromTag(ItemStack[] items, NBTTagList tagList) { + for (int i = 0; i < tagList.tagCount(); i++) { + NBTTagCompound tag = tagList.getCompoundTagAt(i); + int b = tag.getShort("Slot"); + items[b] = ItemStack.loadItemStackFromNBT(tag); + if (tag.hasKey("Quantity")) + items[b].stackSize = ((NBTBase.NBTPrimitive) tag.getTag("Quantity")).func_150287_d(); + } + } + + /** + * Spawns an itemstack in the world at a location + */ + public static void dropItem(ItemStack stack, World world, Vector3 dropLocation) { + EntityItem item = new EntityItem(world, dropLocation.x, dropLocation.y, dropLocation.z, stack); + item.motionX = world.rand.nextGaussian() * 0.05; + item.motionY = world.rand.nextGaussian() * 0.05 + 0.2F; + item.motionZ = world.rand.nextGaussian() * 0.05; + world.spawnEntityInWorld(item); + } + + /** + * Copies an itemstack with a new quantity + */ + public static ItemStack copyStack(ItemStack stack, int quantity) { + if (stack == null) return null; + + stack = stack.copy(); + stack.stackSize = quantity; + return stack; + } + + /** + * Gets the maximum quantity of an item that can be inserted into inv + */ + public static int getInsertibleQuantity(InventoryRange inv, ItemStack stack) { + int quantity = 0; + stack = copyStack(stack, Integer.MAX_VALUE); + for (int slot : inv.slots) quantity += fitStackInSlot(inv, slot, stack); + + return quantity; + } + + public static int getInsertibleQuantity(IInventory inv, ItemStack stack) { + return getInsertibleQuantity(new InventoryRange(inv), stack); + } + + public static int fitStackInSlot(InventoryRange inv, int slot, ItemStack stack) { + ItemStack base = inv.inv.getStackInSlot(slot); + if (!canStack(base, stack) || !inv.canInsertItem(slot, stack)) return 0; + + int fit = base != null ? incrStackSize(base, inv.inv.getInventoryStackLimit() - base.stackSize) + : inv.inv.getInventoryStackLimit(); + return Math.min(fit, stack.stackSize); + } + + public static int fitStackInSlot(IInventory inv, int slot, ItemStack stack) { + return fitStackInSlot(new InventoryRange(inv), slot, stack); + } + + /** + * @param simulate If set to true, no items will actually be inserted + * @return The number of items unable to be inserted + */ + public static int insertItem(InventoryRange inv, ItemStack stack, boolean simulate) { + stack = stack.copy(); + for (int pass = 0; pass < 2; pass++) { + for (int slot : inv.slots) { + ItemStack base = inv.inv.getStackInSlot(slot); + if ((pass == 0) == (base == null)) continue; + int fit = fitStackInSlot(inv, slot, stack); + if (fit == 0) continue; + + if (base != null) { + stack.stackSize -= fit; + if (!simulate) { + base.stackSize += fit; + inv.inv.setInventorySlotContents(slot, base); + } + } else { + if (!simulate) inv.inv.setInventorySlotContents(slot, copyStack(stack, fit)); + stack.stackSize -= fit; + } + if (stack.stackSize == 0) return 0; + } + } + return stack.stackSize; + } + + public static int insertItem(IInventory inv, ItemStack stack, boolean simulate) { + return insertItem(new InventoryRange(inv), stack, simulate); + } + + /** + * Gets the stack in slot if it can be extracted + */ + public static ItemStack getExtractableStack(InventoryRange inv, int slot) { + ItemStack stack = inv.inv.getStackInSlot(slot); + if (stack == null || !inv.canExtractItem(slot, stack)) return null; + + return stack; + } + + public static ItemStack getExtractableStack(IInventory inv, int slot) { + return getExtractableStack(new InventoryRange(inv), slot); + } + + public static boolean areStacksIdentical(ItemStack stack1, ItemStack stack2) { + if (stack1 == null || stack2 == null) return stack1 == stack2; + + return stack1.getItem() == stack2.getItem() && stack1.getItemDamage() == stack2.getItemDamage() + && stack1.stackSize == stack2.stackSize + && Objects.equal(stack1.getTagCompound(), stack2.getTagCompound()); + } + + /** + * Gets an IInventory from a coordinate with support for double chests + */ + public static IInventory getInventory(World world, int x, int y, int z) { + TileEntity tile = world.getTileEntity(x, y, z); + if (!(tile instanceof IInventory)) return null; + + if (tile instanceof TileEntityChest) return getChest((TileEntityChest) tile); + return (IInventory) tile; + } + + public static final ForgeDirection[] chestSides = new ForgeDirection[] { ForgeDirection.WEST, ForgeDirection.EAST, + ForgeDirection.NORTH, ForgeDirection.SOUTH }; + + public static IInventory getChest(TileEntityChest chest) { + for (ForgeDirection fside : chestSides) { + if (chest.getWorldObj() + .getBlock(chest.xCoord + fside.offsetX, chest.yCoord + fside.offsetY, chest.zCoord + fside.offsetZ) + == chest.getBlockType()) + return new InventoryLargeChest( + "container.chestDouble", + (TileEntityChest) chest.getWorldObj().getTileEntity( + chest.xCoord + fside.offsetX, + chest.yCoord + fside.offsetY, + chest.zCoord + fside.offsetZ), + chest); + } + return chest; + } + + public static boolean canStack(ItemStack stack1, ItemStack stack2) { + return stack1 == null || stack2 == null + || (stack1.getItem() == stack2.getItem() + && (!stack2.getHasSubtypes() || stack2.getItemDamage() == stack1.getItemDamage()) + && ItemStack.areItemStackTagsEqual(stack2, stack1)) && stack1.isStackable(); + } + + /** + * Consumes one item from slot in inv with support for containers. + */ + public static void consumeItem(IInventory inv, int slot) { + ItemStack stack = inv.getStackInSlot(slot); + Item item = stack.getItem(); + if (item.hasContainerItem(stack)) { + ItemStack container = item.getContainerItem(stack); + inv.setInventorySlotContents(slot, container); + } else { + inv.decrStackSize(slot, 1); + } + } + + /** + * Gets the size of the stack in a slot. Returns 0 on null stacks + */ + public static int stackSize(IInventory inv, int slot) { + ItemStack stack = inv.getStackInSlot(slot); + return stack == null ? 0 : stack.stackSize; + } + + /** + * Drops all items from inv using getStackInSlotOnClosing + */ + public static void dropOnClose(EntityPlayer player, IInventory inv) { + for (int i = 0; i < inv.getSizeInventory(); i++) { + ItemStack stack = inv.getStackInSlotOnClosing(i); + if (stack != null) player.dropPlayerItemWithRandomChoice(stack, false); + } + } + + public static NBTTagCompound savePersistant(ItemStack stack, NBTTagCompound tag) { + stack.writeToNBT(tag); + tag.removeTag("id"); + tag.setString("name", Item.itemRegistry.getNameForObject(stack.getItem())); + return tag; + } + + public static ItemStack loadPersistant(NBTTagCompound tag) { + String name = tag.getString("name"); + Item item = (Item) Item.itemRegistry.getObject(name); + if (item == null) return null; + int count = tag.hasKey("Count") ? tag.getByte("Count") : 1; + int damage = tag.hasKey("Damage") ? tag.getShort("Damage") : 0; + ItemStack stack = new ItemStack(item, count, damage); + if (tag.hasKey("tag", 10)) stack.stackTagCompound = tag.getCompoundTag("tag"); + return stack; + } +} diff --git a/src/main/java/codechicken/lib/inventory/ItemKey.java b/src/main/java/codechicken/lib/inventory/ItemKey.java new file mode 100644 index 0000000..dd0881a --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/ItemKey.java @@ -0,0 +1,63 @@ +package codechicken.lib.inventory; + +import static codechicken.lib.inventory.InventoryUtils.actualDamage; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.oredict.OreDictionary; + +import com.google.common.base.Objects; + +/** + * Comparable ItemStack with a hashCode implementation. + */ +public class ItemKey implements Comparable { + + public ItemStack stack; + private int hashcode = 0; + + public ItemKey(ItemStack k) { + stack = k; + } + + public ItemKey(Item item, int damage) { + this(new ItemStack(item, 1, damage)); + } + + public ItemKey(Item item, NBTTagCompound tag) { + this(item, OreDictionary.WILDCARD_VALUE, tag); + } + + public ItemKey(Item item, int damage, NBTTagCompound tag) { + this(item, damage); + stack.setTagCompound(tag); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ItemKey)) return false; + + ItemKey k = (ItemKey) obj; + return stack.getItem() == k.stack.getItem() && actualDamage(stack) == actualDamage(k.stack) + && Objects.equal(stack.stackTagCompound, k.stack.stackTagCompound); + } + + @Override + public int hashCode() { + return hashcode != 0 ? hashcode + : (hashcode = Objects.hashCode(stack.getItem(), actualDamage(stack), stack.stackTagCompound)); + } + + public int compareInt(int a, int b) { + return a == b ? 0 : a < b ? -1 : 1; + } + + @Override + public int compareTo(ItemKey o) { + if (stack.getItem() != o.stack.getItem()) + return compareInt(Item.getIdFromItem(stack.getItem()), Item.getIdFromItem(o.stack.getItem())); + if (actualDamage(stack) != actualDamage(o.stack)) return compareInt(actualDamage(stack), actualDamage(o.stack)); + return 0; + } +} diff --git a/src/main/java/codechicken/lib/inventory/SlotDummy.java b/src/main/java/codechicken/lib/inventory/SlotDummy.java new file mode 100644 index 0000000..43f298a --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/SlotDummy.java @@ -0,0 +1,55 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class SlotDummy extends SlotHandleClicks { + + public final int stackLimit; + + public SlotDummy(IInventory inv, int slot, int x, int y) { + this(inv, slot, x, y, 64); + } + + public SlotDummy(IInventory inv, int slot, int x, int y, int limit) { + super(inv, slot, x, y); + stackLimit = limit; + } + + @Override + public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) { + ItemStack held = player.inventory.getItemStack(); + boolean shift = modifier == 1; + slotClick(held, button, shift); + return null; + } + + public void slotClick(ItemStack held, int button, boolean shift) { + ItemStack tstack = getStack(); + if (held != null && (tstack == null || !InventoryUtils.canStack(held, tstack))) { + int quantity = Math.min(held.stackSize, stackLimit); + if (shift) quantity = Math.min(stackLimit, held.getMaxStackSize() * 16); + if (button == 1) quantity = 1; + putStack(InventoryUtils.copyStack(held, quantity)); + } else if (tstack != null) { + int inc; + if (held != null) { + inc = button == 1 ? -held.stackSize : held.stackSize; + if (shift) inc *= 16; + } else { + inc = button == 1 ? -1 : 1; + if (shift) inc *= 16; + } + int quantity = tstack.stackSize + inc; + if (quantity <= 0) putStack(null); + else putStack(InventoryUtils.copyStack(tstack, quantity)); + } + } + + @Override + public void putStack(ItemStack stack) { + if (stack != null && stack.stackSize > stackLimit) stack = InventoryUtils.copyStack(stack, stackLimit); + super.putStack(stack); + } +} diff --git a/src/main/java/codechicken/lib/inventory/SlotDummyOutput.java b/src/main/java/codechicken/lib/inventory/SlotDummyOutput.java new file mode 100644 index 0000000..07c77fe --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/SlotDummyOutput.java @@ -0,0 +1,17 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class SlotDummyOutput extends SlotHandleClicks { + + public SlotDummyOutput(IInventory inv, int slot, int x, int y) { + super(inv, slot, x, y); + } + + @Override + public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) { + return null; + } +} diff --git a/src/main/java/codechicken/lib/inventory/SlotHandleClicks.java b/src/main/java/codechicken/lib/inventory/SlotHandleClicks.java new file mode 100644 index 0000000..50e5a70 --- /dev/null +++ b/src/main/java/codechicken/lib/inventory/SlotHandleClicks.java @@ -0,0 +1,15 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +public abstract class SlotHandleClicks extends Slot { + + public SlotHandleClicks(IInventory inv, int slot, int x, int y) { + super(inv, slot, x, y); + } + + public abstract ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier); +} diff --git a/src/main/java/codechicken/lib/lighting/LC.java b/src/main/java/codechicken/lib/lighting/LC.java new file mode 100644 index 0000000..d7b8d28 --- /dev/null +++ b/src/main/java/codechicken/lib/lighting/LC.java @@ -0,0 +1,87 @@ +package codechicken.lib.lighting; + +import codechicken.lib.render.CCModel; +import codechicken.lib.util.Copyable; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Vector3; + +public class LC implements Copyable { + + public int side; + public float fa; + public float fb; + public float fc; + public float fd; + + public LC() { + this(0, 0, 0, 0, 0); + } + + public LC(int s, float a, float b, float c, float d) { + side = s; + fa = a; + fb = b; + fc = c; + fd = d; + } + + public LC set(int s, float a, float b, float c, float d) { + side = s; + fa = a; + fb = b; + fc = c; + fd = d; + return this; + } + + public LC set(LC lc) { + return set(lc.side, lc.fa, lc.fb, lc.fc, lc.fd); + } + + public LC compute(Vector3 vec, Vector3 normal) { + int side = CCModel.findSide(normal); + if (side < 0) return set(12, 1, 0, 0, 0); + return compute(vec, side); + } + + public LC compute(Vector3 vec, int side) { + boolean offset = false; + switch (side) { + case 0: + offset = vec.y <= 0; + break; + case 1: + offset = vec.y >= 1; + break; + case 2: + offset = vec.z <= 0; + break; + case 3: + offset = vec.z >= 1; + break; + case 4: + offset = vec.x <= 0; + break; + case 5: + offset = vec.x >= 1; + break; + } + if (!offset) side += 6; + return computeO(vec, side); + } + + public LC computeO(Vector3 vec, int side) { + Vector3 v1 = Rotation.axes[((side & 0xE) + 3) % 6]; + Vector3 v2 = Rotation.axes[((side & 0xE) + 5) % 6]; + float d1 = (float) vec.scalarProject(v1); + float d2 = 1 - d1; + float d3 = (float) vec.scalarProject(v2); + float d4 = 1 - d3; + return set(side, d2 * d4, d2 * d3, d1 * d4, d1 * d3); + } + + @Override + public LC copy() { + return new LC(side, fa, fb, fc, fd); + } +} diff --git a/src/main/java/codechicken/lib/lighting/LightMatrix.java b/src/main/java/codechicken/lib/lighting/LightMatrix.java new file mode 100644 index 0000000..ea7054e --- /dev/null +++ b/src/main/java/codechicken/lib/lighting/LightMatrix.java @@ -0,0 +1,139 @@ +package codechicken.lib.lighting; + +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.world.IBlockAccess; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.BlockCoord; + +/** + * Note that when using the class as a vertex transformer, the vertices are assumed to be within the BB (x, y, z) -> + * (x+1, y+1, z+1) + */ +public class LightMatrix implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + + public int computed = 0; + public float[][] ao = new float[13][4]; + public int[][] brightness = new int[13][4]; + + public IBlockAccess access; + public BlockCoord pos = new BlockCoord(); + + private int sampled = 0; + private float[] aSamples = new float[27]; + private int[] bSamples = new int[27]; + + /** + * The 9 positions in the sample array for each side, sides >= 6 are centered on sample 13 (the block itself) + */ + public static final int[][] ssamplem = new int[][] { { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, + { 18, 19, 20, 21, 22, 23, 24, 25, 26 }, { 0, 9, 18, 1, 10, 19, 2, 11, 20 }, + { 6, 15, 24, 7, 16, 25, 8, 17, 26 }, { 0, 3, 6, 9, 12, 15, 18, 21, 24 }, + { 2, 5, 8, 11, 14, 17, 20, 23, 26 }, { 9, 10, 11, 12, 13, 14, 15, 16, 17 }, + { 9, 10, 11, 12, 13, 14, 15, 16, 17 }, { 3, 12, 21, 4, 13, 22, 5, 14, 23 }, + { 3, 12, 21, 4, 13, 22, 5, 14, 23 }, { 1, 4, 7, 10, 13, 16, 19, 22, 25 }, + { 1, 4, 7, 10, 13, 16, 19, 22, 25 }, { 13, 13, 13, 13, 13, 13, 13, 13, 13 } }; + + public static final int[][] qsamplem = new int[][] { // the positions in the side sample array for each corner + { 0, 1, 3, 4 }, { 5, 1, 2, 4 }, { 6, 7, 3, 4 }, { 5, 7, 8, 4 } }; + public static final float[] sideao = new float[] { 0.5F, 1F, 0.8F, 0.8F, 0.6F, 0.6F, 0.5F, 1F, 0.8F, 0.8F, 0.6F, + 0.6F, 1F }; + + /* + * static { int[][] os = new int[][]{ {0,-1,0}, {0, 1,0}, {0,0,-1}, {0,0, 1}, {-1,0,0}, { 1,0,0}}; for(int s = 0; s + * < 12; s++) { int[] d0 = s < 6 ? new int[]{os[s][0]+1, os[s][1]+1, os[s][2]+1} : new int[]{1, 1, 1}; int[] d1 = + * os[((s&0xE)+3)%6]; int[] d2 = os[((s&0xE)+5)%6]; for(int a = -1; a <= 1; a++) for(int b = -1; b <= 1; b++) + * ssamplem[s][(a+1)*3+b+1] = (d0[1]+d1[1]*a+d2[1]*b)*9+(d0[2]+d1[2]*a+d2[2]*b)*3+(d0[0]+d1[0]*a+d2[0]*b); } + * System.out.println(Arrays.deepToString(ssamplem)); } + */ + + public void locate(IBlockAccess a, int x, int y, int z) { + access = a; + pos.set(x, y, z); + computed = 0; + sampled = 0; + } + + public void sample(int i) { + if ((sampled & 1 << i) == 0) { + int x = pos.x + (i % 3) - 1; + int y = pos.y + (i / 9) - 1; + int z = pos.z + (i / 3 % 3) - 1; + Block b = access.getBlock(x, y, z); + bSamples[i] = access.getLightBrightnessForSkyBlocks(x, y, z, b.getLightValue(access, x, y, z)); + aSamples[i] = b.getAmbientOcclusionLightValue(); + sampled |= 1 << i; + } + } + + public int[] brightness(int side) { + sideSample(side); + return brightness[side]; + } + + public float[] ao(int side) { + sideSample(side); + return ao[side]; + } + + public void sideSample(int side) { + if ((computed & 1 << side) == 0) { + int[] ssample = ssamplem[side]; + for (int q = 0; q < 4; q++) { + int[] qsample = qsamplem[q]; + if (Minecraft.isAmbientOcclusionEnabled()) + interp(side, q, ssample[qsample[0]], ssample[qsample[1]], ssample[qsample[2]], ssample[qsample[3]]); + else interp(side, q, ssample[4], ssample[4], ssample[4], ssample[4]); + } + computed |= 1 << side; + } + } + + private void interp(int s, int q, int a, int b, int c, int d) { + sample(a); + sample(b); + sample(c); + sample(d); + ao[s][q] = interpAO(aSamples[a], aSamples[b], aSamples[c], aSamples[d]) * sideao[s]; + brightness[s][q] = interpBrightness(bSamples[a], bSamples[b], bSamples[c], bSamples[d]); + } + + public static float interpAO(float a, float b, float c, float d) { + return (a + b + c + d) / 4F; + } + + public static int interpBrightness(int a, int b, int c, int d) { + if (a == 0) a = d; + if (b == 0) b = d; + if (c == 0) c = d; + return (a + b + c + d) >> 2 & 0xFF00FF; + } + + @Override + public boolean load(CCRenderState state) { + if (!state.computeLighting) return false; + + state.pipeline.addDependency(state.colourAttrib); + state.pipeline.addDependency(state.lightCoordAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + LC lc = state.lc; + float[] a = ao(lc.side); + float f = (a[0] * lc.fa + a[1] * lc.fb + a[2] * lc.fc + a[3] * lc.fd); + int[] b = brightness(lc.side); + state.setColourInstance(ColourRGBA.multiplyC(state.colour, f)); + state.setBrightnessInstance((int) (b[0] * lc.fa + b[1] * lc.fb + b[2] * lc.fc + b[3] * lc.fd) & 0xFF00FF); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/src/main/java/codechicken/lib/lighting/LightModel.java b/src/main/java/codechicken/lib/lighting/LightModel.java new file mode 100644 index 0000000..299c68c --- /dev/null +++ b/src/main/java/codechicken/lib/lighting/LightModel.java @@ -0,0 +1,104 @@ +package codechicken.lib.lighting; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Vector3; + +public class LightModel implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + + public static class Light { + + public Vector3 ambient = new Vector3(); + public Vector3 diffuse = new Vector3(); + public Vector3 position; + + public Light(Vector3 pos) { + position = pos.copy().normalize(); + } + + public Light setDiffuse(Vector3 vec) { + diffuse.set(vec); + return this; + } + + public Light setAmbient(Vector3 vec) { + ambient.set(vec); + return this; + } + } + + public static LightModel standardLightModel; + + static { + standardLightModel = new LightModel().setAmbient(new Vector3(0.4, 0.4, 0.4)) + .addLight(new Light(new Vector3(0.2, 1, -0.7)).setDiffuse(new Vector3(0.6, 0.6, 0.6))) + .addLight(new Light(new Vector3(-0.2, 1, 0.7)).setDiffuse(new Vector3(0.6, 0.6, 0.6))); + } + + private Vector3 ambient = new Vector3(); + private Light[] lights = new Light[8]; + private int lightCount; + + public LightModel addLight(Light light) { + lights[lightCount++] = light; + return this; + } + + public LightModel setAmbient(Vector3 vec) { + ambient.set(vec); + return this; + } + + /** + * @param colour The pre-lighting vertex colour. RGBA format + * @param normal The normal at the vertex + * @return The lighting applied colour + */ + public int apply(int colour, Vector3 normal) { + Vector3 n_colour = ambient.copy(); + for (int l = 0; l < lightCount; l++) { + Light light = lights[l]; + double n_l = light.position.dotProduct(normal); + double f = n_l > 0 ? 1 : 0; + n_colour.x += light.ambient.x + f * light.diffuse.x * n_l; + n_colour.y += light.ambient.y + f * light.diffuse.y * n_l; + n_colour.z += light.ambient.z + f * light.diffuse.z * n_l; + } + + if (n_colour.x > 1) n_colour.x = 1; + if (n_colour.y > 1) n_colour.y = 1; + if (n_colour.z > 1) n_colour.z = 1; + + n_colour.multiply((colour >>> 24) / 255D, (colour >> 16 & 0xFF) / 255D, (colour >> 8 & 0xFF) / 255D); + return (int) (n_colour.x * 255) << 24 | (int) (n_colour.y * 255) << 16 + | (int) (n_colour.z * 255) << 8 + | colour & 0xFF; + } + + @Override + public boolean load(CCRenderState state) { + if (!state.computeLighting) return false; + + state.pipeline.addDependency(CCRenderState.normalAttrib()); + state.pipeline.addDependency(CCRenderState.colourAttrib()); + return true; + } + + @Override + public void operate(CCRenderState state) { + state.setColourInstance(apply(state.colour, state.normal)); + } + + @Override + public int operationID() { + return operationIndex; + } + + public PlanarLightModel reducePlanar() { + int[] colours = new int[6]; + for (int i = 0; i < 6; i++) colours[i] = apply(-1, Rotation.axes[i]); + return new PlanarLightModel(colours); + } +} diff --git a/src/main/java/codechicken/lib/lighting/PlanarLightMatrix.java b/src/main/java/codechicken/lib/lighting/PlanarLightMatrix.java new file mode 100644 index 0000000..c75456c --- /dev/null +++ b/src/main/java/codechicken/lib/lighting/PlanarLightMatrix.java @@ -0,0 +1,57 @@ +package codechicken.lib.lighting; + +import net.minecraft.block.Block; +import net.minecraft.world.IBlockAccess; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.BlockCoord; + +public class PlanarLightMatrix extends PlanarLightModel { + + public static final int operationIndex = CCRenderState.registerOperation(); + public static PlanarLightMatrix instance = new PlanarLightMatrix(); + + public IBlockAccess access; + public BlockCoord pos = new BlockCoord(); + + private int sampled = 0; + public int[] brightness = new int[6]; + + public PlanarLightMatrix() { + super(PlanarLightModel.standardLightModel.colours); + } + + public PlanarLightMatrix locate(IBlockAccess a, int x, int y, int z) { + access = a; + pos.set(x, y, z); + sampled = 0; + return this; + } + + public int brightness(int side) { + if ((sampled & 1 << side) == 0) { + Block b = access.getBlock(pos.x, pos.y, pos.z); + brightness[side] = access + .getLightBrightnessForSkyBlocks(pos.x, pos.y, pos.z, b.getLightValue(access, pos.x, pos.y, pos.z)); + sampled |= 1 << side; + } + return brightness[side]; + } + + @Override + public boolean load(CCRenderState state) { + state.pipeline.addDependency(CCRenderState.sideAttrib()); + return true; + } + + @Override + public void operate(CCRenderState state) { + super.operate(state); + state.setBrightnessInstance(brightness(state.side)); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/src/main/java/codechicken/lib/lighting/PlanarLightModel.java b/src/main/java/codechicken/lib/lighting/PlanarLightModel.java new file mode 100644 index 0000000..93f777f --- /dev/null +++ b/src/main/java/codechicken/lib/lighting/PlanarLightModel.java @@ -0,0 +1,37 @@ +package codechicken.lib.lighting; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.render.CCRenderState; + +/** + * Faster precomputed version of LightModel that only works for axis planar sides + */ +public class PlanarLightModel implements CCRenderState.IVertexOperation { + + public static PlanarLightModel standardLightModel = LightModel.standardLightModel.reducePlanar(); + + public int[] colours; + + public PlanarLightModel(int[] colours) { + this.colours = colours; + } + + @Override + public boolean load(CCRenderState state) { + if (!state.computeLighting) return false; + + state.pipeline.addDependency(CCRenderState.sideAttrib()); + state.pipeline.addDependency(CCRenderState.colourAttrib()); + return true; + } + + @Override + public void operate(CCRenderState state) { + state.setColourInstance(ColourRGBA.multiply(state.colour, colours[state.side])); + } + + @Override + public int operationID() { + return LightModel.operationIndex; + } +} diff --git a/src/main/java/codechicken/lib/lighting/SimpleBrightnessModel.java b/src/main/java/codechicken/lib/lighting/SimpleBrightnessModel.java new file mode 100644 index 0000000..7c5d15d --- /dev/null +++ b/src/main/java/codechicken/lib/lighting/SimpleBrightnessModel.java @@ -0,0 +1,56 @@ +package codechicken.lib.lighting; + +import net.minecraft.block.Block; +import net.minecraft.world.IBlockAccess; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.BlockCoord; + +/** + * Faster precomputed version of LightModel that only works for axis planar sides + */ +public class SimpleBrightnessModel implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + public static SimpleBrightnessModel instance = new SimpleBrightnessModel(); + + public IBlockAccess access; + public BlockCoord pos = new BlockCoord(); + + private int sampled = 0; + private final int[] samples = new int[6]; + private final BlockCoord c = new BlockCoord(); + + public void locate(IBlockAccess a, int x, int y, int z) { + access = a; + pos.set(x, y, z); + sampled = 0; + } + + public int sample(int side) { + if ((sampled & 1 << side) == 0) { + c.set(pos).offset(side); + Block block = access.getBlock(c.x, c.y, c.z); + samples[side] = access + .getLightBrightnessForSkyBlocks(c.x, c.y, c.z, block.getLightValue(access, c.x, c.y, c.z)); + sampled |= 1 << side; + } + return samples[side]; + } + + @Override + public boolean load(CCRenderState state) { + state.pipeline.addDependency(CCRenderState.sideAttrib()); + return true; + } + + @Override + public void operate(CCRenderState state) { + state.setBrightnessInstance(sample(state.side)); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/src/main/java/codechicken/lib/math/MathHelper.java b/src/main/java/codechicken/lib/math/MathHelper.java new file mode 100644 index 0000000..94220a0 --- /dev/null +++ b/src/main/java/codechicken/lib/math/MathHelper.java @@ -0,0 +1,152 @@ +package codechicken.lib.math; + +public class MathHelper { + + public static final double phi = 1.618033988749894; + public static final double pi = Math.PI; + public static final double todeg = 57.29577951308232; + public static final double torad = 0.017453292519943; + public static final double sqrt2 = 1.414213562373095; + + public static double[] SIN_TABLE = new double[65536]; + + static { + for (int i = 0; i < 65536; ++i) SIN_TABLE[i] = Math.sin(i / 65536D * 2 * Math.PI); + + SIN_TABLE[0] = 0; + SIN_TABLE[16384] = 1; + SIN_TABLE[32768] = 0; + SIN_TABLE[49152] = 1; + } + + public static double sin(double d) { + return SIN_TABLE[(int) ((float) d * 10430.378F) & 65535]; + } + + public static double cos(double d) { + return SIN_TABLE[(int) ((float) d * 10430.378F + 16384.0F) & 65535]; + } + + /** + * @param a The value + * @param b The value to approach + * @param max The maximum step + * @return the closed value to b no less than max from a + */ + public static float approachLinear(float a, float b, float max) { + return (a > b) ? (a - b < max ? b : a - max) : (b - a < max ? b : a + max); + } + + /** + * @param a The value + * @param b The value to approach + * @param max The maximum step + * @return the closed value to b no less than max from a + */ + public static double approachLinear(double a, double b, double max) { + return (a > b) ? (a - b < max ? b : a - max) : (b - a < max ? b : a + max); + } + + /** + * @param a The first value + * @param b The second value + * @param d The interpolation factor, between 0 and 1 + * @return a+(b-a)*d + */ + public static float interpolate(float a, float b, float d) { + return a + (b - a) * d; + } + + /** + * @param a The first value + * @param b The second value + * @param d The interpolation factor, between 0 and 1 + * @return a+(b-a)*d + */ + public static double interpolate(double a, double b, double d) { + return a + (b - a) * d; + } + + /** + * @param a The value + * @param b The value to approach + * @param ratio The ratio to reduce the difference by + * @return a+(b-a)*ratio + */ + public static double approachExp(double a, double b, double ratio) { + return a + (b - a) * ratio; + } + + /** + * @param a The value + * @param b The value to approach + * @param ratio The ratio to reduce the difference by + * @param cap The maximum amount to advance by + * @return a+(b-a)*ratio + */ + public static double approachExp(double a, double b, double ratio, double cap) { + double d = (b - a) * ratio; + if (Math.abs(d) > cap) d = Math.signum(d) * cap; + return a + d; + } + + /** + * @param a The value + * @param b The value to approach + * @param ratio The ratio to reduce the difference by + * @param c The value to retreat from + * @param kick The difference when a == c + * @return + */ + public static double retreatExp(double a, double b, double c, double ratio, double kick) { + double d = (Math.abs(c - a) + kick) * ratio; + if (d > Math.abs(b - a)) return b; + return a + Math.signum(b - a) * d; + } + + /** + * + * @param value The value + * @param min The min value + * @param max The max value + * @return The clipped value between min and max + */ + public static double clip(double value, double min, double max) { + if (value > max) value = max; + if (value < min) value = min; + return value; + } + + /** + * @return a <= x <= b + */ + public static boolean between(double a, double x, double b) { + return a <= x && x <= b; + } + + public static int approachExpI(int a, int b, double ratio) { + int r = (int) Math.round(approachExp(a, b, ratio)); + return r == a ? b : r; + } + + public static int retreatExpI(int a, int b, int c, double ratio, int kick) { + int r = (int) Math.round(retreatExp(a, b, c, ratio, kick)); + return r == a ? b : r; + } + + public static int floor_double(double d) { + return net.minecraft.util.MathHelper.floor_double(d); + } + + public static int roundAway(double d) { + return (int) (d < 0 ? Math.floor(d) : Math.ceil(d)); + } + + public static int compare(int a, int b) { + return a == b ? 0 : a < b ? -1 : 1; + } + + public static int compare(double a, double b) { + return a == b ? 0 : a < b ? -1 : 1; + } +} diff --git a/src/main/java/codechicken/lib/packet/ICustomPacketTile.java b/src/main/java/codechicken/lib/packet/ICustomPacketTile.java new file mode 100644 index 0000000..0758be7 --- /dev/null +++ b/src/main/java/codechicken/lib/packet/ICustomPacketTile.java @@ -0,0 +1,6 @@ +package codechicken.lib.packet; + +public interface ICustomPacketTile { + + public void handleDescriptionPacket(PacketCustom packet); +} diff --git a/src/main/java/codechicken/lib/packet/PacketCustom.java b/src/main/java/codechicken/lib/packet/PacketCustom.java new file mode 100644 index 0000000..67753d4 --- /dev/null +++ b/src/main/java/codechicken/lib/packet/PacketCustom.java @@ -0,0 +1,553 @@ +package codechicken.lib.packet; + +import java.util.EnumMap; +import java.util.List; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.INetHandler; +import net.minecraft.network.NetHandlerPlayServer; +import net.minecraft.network.Packet; +import net.minecraft.network.play.INetHandlerPlayClient; +import net.minecraft.network.play.INetHandlerPlayServer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.management.PlayerManager; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; + +import com.google.common.collect.Maps; + +import codechicken.lib.data.MCDataInput; +import codechicken.lib.data.MCDataOutput; +import codechicken.lib.vec.BlockCoord; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.network.ByteBufUtils; +import cpw.mods.fml.common.network.FMLEmbeddedChannel; +import cpw.mods.fml.common.network.FMLOutboundHandler; +import cpw.mods.fml.common.network.NetworkHandshakeEstablished; +import cpw.mods.fml.common.network.NetworkRegistry; +import cpw.mods.fml.common.network.handshake.NetworkDispatcher; +import cpw.mods.fml.common.network.internal.FMLProxyPacket; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.util.AttributeKey; + +public final class PacketCustom implements MCDataInput, MCDataOutput { + + public static interface ICustomPacketHandler { + } + + public interface IClientPacketHandler extends ICustomPacketHandler { + + public void handlePacket(PacketCustom packetCustom, Minecraft mc, INetHandlerPlayClient handler); + } + + public interface IServerPacketHandler extends ICustomPacketHandler { + + public void handlePacket(PacketCustom packetCustom, EntityPlayerMP sender, INetHandlerPlayServer handler); + } + + public static AttributeKey cclHandler = new AttributeKey("ccl:handler"); + + @ChannelHandler.Sharable + public static class CustomInboundHandler extends SimpleChannelInboundHandler { + + public EnumMap handlers = Maps.newEnumMap(Side.class); + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + super.handlerAdded(ctx); + ctx.channel().attr(cclHandler).set(this); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, FMLProxyPacket msg) throws Exception { + handlers.get(ctx.channel().attr(NetworkRegistry.CHANNEL_SOURCE).get()).handle( + ctx.channel().attr(NetworkRegistry.NET_HANDLER).get(), + ctx.channel().attr(NetworkRegistry.FML_CHANNEL).get(), + new PacketCustom(msg.payload())); + } + } + + private static interface CustomHandler { + + public void handle(INetHandler handler, String channel, PacketCustom packet) throws Exception; + } + + public static class ClientInboundHandler implements CustomHandler { + + private IClientPacketHandler handler; + + public ClientInboundHandler(ICustomPacketHandler handler) { + this.handler = (IClientPacketHandler) handler; + } + + @Override + public void handle(INetHandler netHandler, String channel, PacketCustom packet) throws Exception { + if (netHandler instanceof INetHandlerPlayClient) + handler.handlePacket(packet, Minecraft.getMinecraft(), (INetHandlerPlayClient) netHandler); + else System.err.println("Invalid INetHandler for PacketCustom on channel: " + channel); + } + } + + public static class ServerInboundHandler implements CustomHandler { + + private IServerPacketHandler handler; + + public ServerInboundHandler(ICustomPacketHandler handler) { + this.handler = (IServerPacketHandler) handler; + } + + @Override + public void handle(INetHandler netHandler, String channel, PacketCustom packet) throws Exception { + if (netHandler instanceof NetHandlerPlayServer) handler.handlePacket( + packet, + ((NetHandlerPlayServer) netHandler).playerEntity, + (INetHandlerPlayServer) netHandler); + else System.err.println("Invalid INetHandler for PacketCustom on channel: " + channel); + } + } + + public static interface IHandshakeHandler { + + public void handshakeRecieved(NetHandlerPlayServer netHandler); + } + + public static class HandshakeInboundHandler extends ChannelInboundHandlerAdapter { + + public IHandshakeHandler handler; + + public HandshakeInboundHandler(IHandshakeHandler handler) { + this.handler = handler; + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof NetworkHandshakeEstablished) { + INetHandler netHandler = ((NetworkDispatcher) ctx.channel() + .attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).get()).getNetHandler(); + if (netHandler instanceof NetHandlerPlayServer) + handler.handshakeRecieved((NetHandlerPlayServer) netHandler); + } else ctx.fireUserEventTriggered(evt); + } + } + + public static String channelName(Object channelKey) { + if (channelKey instanceof String) return (String) channelKey; + + ModContainer mc = channelKey instanceof ModContainer ? (ModContainer) channelKey + : FMLCommonHandler.instance().findContainerFor(channelKey); + if (mc != null) { + String s = mc.getModId(); + if (s.length() > 20) throw new IllegalArgumentException( + "Mod ID (" + s + ") too long for use as channel (20 chars). Use a string identifier"); + return s; + } + + throw new IllegalArgumentException("Invalid channel: " + channelKey); + } + + public static FMLEmbeddedChannel getOrCreateChannel(String channelName, Side side) { + if (!NetworkRegistry.INSTANCE.hasChannel(channelName, side)) + NetworkRegistry.INSTANCE.newChannel(channelName, new CustomInboundHandler()); + return NetworkRegistry.INSTANCE.getChannel(channelName, side); + } + + public static void assignHandler(Object channelKey, ICustomPacketHandler handler) { + String channelName = channelName(channelKey); + Side side = handler instanceof IServerPacketHandler ? Side.SERVER : Side.CLIENT; + FMLEmbeddedChannel channel = getOrCreateChannel(channelName, side); + channel.attr(cclHandler).get().handlers + .put(side, side == Side.SERVER ? new ServerInboundHandler(handler) : new ClientInboundHandler(handler)); + } + + public static void assignHandshakeHandler(Object channelKey, IHandshakeHandler handler) { + FMLEmbeddedChannel channel = getOrCreateChannel(channelName(channelKey), Side.SERVER); + channel.pipeline().addLast(new HandshakeInboundHandler(handler)); + } + + private ByteBuf byteBuf; + private String channel; + private int type; + + public PacketCustom(ByteBuf payload) { + byteBuf = payload; + + type = byteBuf.readUnsignedByte(); + if (type > 0x80) decompress(); + type &= 0x7F; + } + + public PacketCustom(Object channelKey, int type) { + if (type <= 0 || type >= 0x80) + throw new IllegalArgumentException("Packet type: " + type + " is not within required 0 < t < 0x80"); + + this.channel = channelName(channelKey); + this.type = type; + byteBuf = Unpooled.buffer(); + byteBuf.writeByte(type); + } + + /** + * Decompresses the remaining ByteBuf (after type has been read) using Snappy + */ + private void decompress() { + Inflater inflater = new Inflater(); + try { + int len = byteBuf.readInt(); + ByteBuf out = Unpooled.buffer(len); + inflater.setInput(byteBuf.array(), byteBuf.readerIndex(), byteBuf.readableBytes()); + inflater.inflate(out.array()); + out.writerIndex(len); + byteBuf = out; + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + inflater.end(); + } + } + + /** + * Compresses the payload ByteBuf after the type byte + */ + private void do_compress() { + Deflater deflater = new Deflater(); + try { + byteBuf.readerIndex(1); + int len = byteBuf.readableBytes(); + deflater.setInput(byteBuf.array(), byteBuf.readerIndex(), len); + deflater.finish(); + ByteBuf out = Unpooled.buffer(len + 5); + int clen = deflater.deflate(out.array(), 5, len); + if (clen >= len - 5 || !deflater.finished()) // not worth compressing, gets larger + return; + + out.setByte(0, type | 0x80); + out.setInt(1, len); + out.writerIndex(clen + 5); + byteBuf = out; + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + byteBuf.readerIndex(0); + deflater.end(); + } + } + + public boolean incoming() { + return channel == null; + } + + public int getType() { + return type & 0x7F; + } + + public ByteBuf getByteBuf() { + return byteBuf; + } + + public PacketCustom compress() { + if (incoming()) throw new IllegalStateException("Tried to compress an incoming packet"); + if ((type & 0x80) != 0) throw new IllegalStateException("Packet already compressed"); + type |= 0x80; + return this; + } + + public PacketCustom writeBoolean(boolean b) { + byteBuf.writeBoolean(b); + return this; + } + + public PacketCustom writeByte(int b) { + byteBuf.writeByte(b); + return this; + } + + public PacketCustom writeShort(int s) { + byteBuf.writeShort(s); + return this; + } + + public PacketCustom writeInt(int i) { + byteBuf.writeInt(i); + return this; + } + + public PacketCustom writeFloat(float f) { + byteBuf.writeFloat(f); + return this; + } + + public PacketCustom writeDouble(double d) { + byteBuf.writeDouble(d); + return this; + } + + public PacketCustom writeLong(long l) { + byteBuf.writeLong(l); + return this; + } + + @Override + public PacketCustom writeChar(char c) { + byteBuf.writeChar(c); + return this; + } + + public PacketCustom writeVarInt(int i) { + ByteBufUtils.writeVarInt(byteBuf, i, 5); + return this; + } + + public PacketCustom writeVarShort(int s) { + ByteBufUtils.writeVarShort(byteBuf, s); + return this; + } + + public PacketCustom writeByteArray(byte[] barray) { + byteBuf.writeBytes(barray); + return this; + } + + public PacketCustom writeString(String s) { + ByteBufUtils.writeUTF8String(byteBuf, s); + return this; + } + + public PacketCustom writeCoord(int x, int y, int z) { + writeInt(x); + writeInt(y); + writeInt(z); + return this; + } + + public PacketCustom writeCoord(BlockCoord coord) { + writeInt(coord.x); + writeInt(coord.y); + writeInt(coord.z); + return this; + } + + public PacketCustom writeItemStack(ItemStack stack) { + writeItemStack(stack, false); + return this; + } + + public PacketCustom writeItemStack(ItemStack stack, boolean large) { + if (stack == null) { + writeInt(-1); + } else { + writeInt(Item.getIdFromItem(stack.getItem())); + if (large) writeInt(stack.stackSize); + else writeByte(stack.stackSize); + writeShort(stack.getItemDamage()); + writeNBTTagCompound(stack.stackTagCompound); + } + return this; + } + + public PacketCustom writeNBTTagCompound(NBTTagCompound compound) { + ByteBufUtils.writeTag(byteBuf, compound); + return this; + } + + public PacketCustom writeFluidStack(FluidStack fluid) { + if (fluid == null) { + writeShort(-1); + } else { + writeShort(fluid.getFluidID()); + writeVarInt(fluid.amount); + writeNBTTagCompound(fluid.tag); + } + return this; + } + + public boolean readBoolean() { + return byteBuf.readBoolean(); + } + + public short readUByte() { + return byteBuf.readUnsignedByte(); + } + + public int readUShort() { + return byteBuf.readUnsignedShort(); + } + + public byte readByte() { + return byteBuf.readByte(); + } + + public short readShort() { + return byteBuf.readShort(); + } + + public int readInt() { + return byteBuf.readInt(); + } + + public float readFloat() { + return byteBuf.readFloat(); + } + + public double readDouble() { + return byteBuf.readDouble(); + } + + public long readLong() { + return byteBuf.readLong(); + } + + public char readChar() { + return byteBuf.readChar(); + } + + @Override + public int readVarShort() { + return ByteBufUtils.readVarShort(byteBuf); + } + + @Override + public int readVarInt() { + return ByteBufUtils.readVarInt(byteBuf, 5); + } + + public BlockCoord readCoord() { + return new BlockCoord(readInt(), readInt(), readInt()); + } + + public byte[] readByteArray(int length) { + byte[] barray = new byte[length]; + byteBuf.readBytes(barray, 0, length); + return barray; + } + + public String readString() { + return ByteBufUtils.readUTF8String(byteBuf); + } + + public ItemStack readItemStack() { + return readItemStack(false); + } + + public ItemStack readItemStack(boolean large) { + ItemStack item = null; + int itemID = readInt(); + + if (itemID >= 0) { + int stackSize = large ? readInt() : readByte(); + short damage = readShort(); + item = new ItemStack(Item.getItemById(itemID), stackSize, damage); + item.stackTagCompound = readNBTTagCompound(); + } + + return item; + } + + public NBTTagCompound readNBTTagCompound() { + return ByteBufUtils.readTag(byteBuf); + } + + public FluidStack readFluidStack() { + Fluid fluid = FluidRegistry.getFluid(readShort()); + if (fluid == null) fluid = FluidRegistry.WATER; + + return new FluidStack(fluid, readVarInt(), readNBTTagCompound()); + } + + public FMLProxyPacket toPacket() { + if (incoming()) throw new IllegalStateException("Tried to write an incoming packet"); + + if (byteBuf.readableBytes() > 32000 || (type & 0x80) != 0) do_compress(); + + // FML packet impl returns the whole of the backing array, copy used portion of array to another ByteBuf + return new FMLProxyPacket(byteBuf.copy(), channel); + } + + public void sendToPlayer(EntityPlayer player) { + sendToPlayer(toPacket(), player); + } + + public static void sendToPlayer(Packet packet, EntityPlayer player) { + if (player == null) sendToClients(packet); + else((EntityPlayerMP) player).playerNetServerHandler.sendPacket(packet); + } + + public void sendToClients() { + sendToClients(toPacket()); + } + + public static void sendToClients(Packet packet) { + MinecraftServer.getServer().getConfigurationManager().sendPacketToAllPlayers(packet); + } + + public void sendPacketToAllAround(double x, double y, double z, double range, int dim) { + sendToAllAround(toPacket(), x, y, z, range, dim); + } + + public static void sendToAllAround(Packet packet, double x, double y, double z, double range, int dim) { + MinecraftServer.getServer().getConfigurationManager().sendToAllNear(x, y, z, range, dim, packet); + } + + public void sendToDimension(int dim) { + sendToDimension(toPacket(), dim); + } + + public static void sendToDimension(Packet packet, int dim) { + MinecraftServer.getServer().getConfigurationManager().sendPacketToAllPlayersInDimension(packet, dim); + } + + public void sendToChunk(World world, int chunkX, int chunkZ) { + sendToChunk(toPacket(), world, chunkX, chunkZ); + } + + public static void sendToChunk(Packet packet, World world, int chunkX, int chunkZ) { + PlayerManager playerManager = ((WorldServer) world).getPlayerManager(); + for (EntityPlayerMP player : (List) MinecraftServer.getServer() + .getConfigurationManager().playerEntityList) + if (playerManager.isPlayerWatchingChunk(player, chunkX, chunkZ)) sendToPlayer(packet, player); + + /* + * Commented until forge accepts access tranformer request PlayerInstance p = ((WorldServer) + * world).getPlayerManager().getOrCreateChunkWatcher(chunkX, chunkZ, false); if (p != null) + * p.sendToAllPlayersWatchingChunk(packet); + */ + } + + public void sendToOps() { + sendToOps(toPacket()); + } + + public static void sendToOps(Packet packet) { + for (EntityPlayerMP player : (List) MinecraftServer.getServer() + .getConfigurationManager().playerEntityList) + if (MinecraftServer.getServer().getConfigurationManager().func_152596_g(player.getGameProfile())) + sendToPlayer(packet, player); + } + + @SideOnly(Side.CLIENT) + public void sendToServer() { + sendToServer(toPacket()); + } + + @SideOnly(Side.CLIENT) + public static void sendToServer(Packet packet) { + Minecraft.getMinecraft().getNetHandler().addToSendQueue(packet); + } +} diff --git a/src/main/java/codechicken/lib/raytracer/ExtendedMOP.java b/src/main/java/codechicken/lib/raytracer/ExtendedMOP.java new file mode 100644 index 0000000..4774d5a --- /dev/null +++ b/src/main/java/codechicken/lib/raytracer/ExtendedMOP.java @@ -0,0 +1,53 @@ +package codechicken.lib.raytracer; + +import net.minecraft.entity.Entity; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.Vec3; + +public class ExtendedMOP extends MovingObjectPosition implements Comparable { + + public Object data; + /** + * The square distance from the start of the raytrace. + */ + public double dist; + + public ExtendedMOP(Entity entity, Object data) { + super(entity); + setData(data); + } + + public ExtendedMOP(int x, int y, int z, int side, Vec3 hit, Object data) { + super(x, y, z, side, hit); + setData(data); + } + + public ExtendedMOP(MovingObjectPosition mop, Object data, double dist) { + super(0, 0, 0, 0, mop.hitVec); + typeOfHit = mop.typeOfHit; + blockX = mop.blockX; + blockY = mop.blockY; + blockZ = mop.blockZ; + sideHit = mop.sideHit; + subHit = mop.subHit; + setData(data); + this.dist = dist; + } + + public void setData(Object data) { + if (data instanceof Integer) subHit = ((Integer) data).intValue(); + this.data = data; + } + + @SuppressWarnings("unchecked") + public static T getData(MovingObjectPosition mop) { + if (mop instanceof ExtendedMOP) return (T) ((ExtendedMOP) mop).data; + + return (T) Integer.valueOf(mop.subHit); + } + + @Override + public int compareTo(ExtendedMOP o) { + return dist == o.dist ? 0 : dist < o.dist ? -1 : 1; + } +} diff --git a/src/main/java/codechicken/lib/raytracer/IndexedCuboid6.java b/src/main/java/codechicken/lib/raytracer/IndexedCuboid6.java new file mode 100644 index 0000000..26651ff --- /dev/null +++ b/src/main/java/codechicken/lib/raytracer/IndexedCuboid6.java @@ -0,0 +1,13 @@ +package codechicken.lib.raytracer; + +import codechicken.lib.vec.Cuboid6; + +public class IndexedCuboid6 extends Cuboid6 { + + public Object data; + + public IndexedCuboid6(Object data, Cuboid6 cuboid) { + super(cuboid); + this.data = data; + } +} diff --git a/src/main/java/codechicken/lib/raytracer/RayTracer.java b/src/main/java/codechicken/lib/raytracer/RayTracer.java new file mode 100644 index 0000000..d06c0fb --- /dev/null +++ b/src/main/java/codechicken/lib/raytracer/RayTracer.java @@ -0,0 +1,193 @@ +package codechicken.lib.raytracer; + +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.MovingObjectPosition.MovingObjectType; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.vec.BlockCoord; +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.Vector3; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class RayTracer { + + private Vector3 vec = new Vector3(); + private Vector3 vec2 = new Vector3(); + + private Vector3 s_vec = new Vector3(); + private double s_dist; + private int s_side; + private IndexedCuboid6 c_cuboid; + + private static ThreadLocal t_inst = new ThreadLocal(); + + public static RayTracer instance() { + RayTracer inst = t_inst.get(); + if (inst == null) t_inst.set(inst = new RayTracer()); + return inst; + } + + private void traceSide(int side, Vector3 start, Vector3 end, Cuboid6 cuboid) { + vec.set(start); + Vector3 hit = null; + switch (side) { + case 0: + hit = vec.XZintercept(end, cuboid.min.y); + break; + case 1: + hit = vec.XZintercept(end, cuboid.max.y); + break; + case 2: + hit = vec.XYintercept(end, cuboid.min.z); + break; + case 3: + hit = vec.XYintercept(end, cuboid.max.z); + break; + case 4: + hit = vec.YZintercept(end, cuboid.min.x); + break; + case 5: + hit = vec.YZintercept(end, cuboid.max.x); + break; + } + if (hit == null) return; + + switch (side) { + case 0: + case 1: + if (!MathHelper.between(cuboid.min.x, hit.x, cuboid.max.x) + || !MathHelper.between(cuboid.min.z, hit.z, cuboid.max.z)) + return; + break; + case 2: + case 3: + if (!MathHelper.between(cuboid.min.x, hit.x, cuboid.max.x) + || !MathHelper.between(cuboid.min.y, hit.y, cuboid.max.y)) + return; + break; + case 4: + case 5: + if (!MathHelper.between(cuboid.min.y, hit.y, cuboid.max.y) + || !MathHelper.between(cuboid.min.z, hit.z, cuboid.max.z)) + return; + break; + } + + double dist = vec2.set(hit).subtract(start).magSquared(); + if (dist < s_dist) { + s_side = side; + s_dist = dist; + s_vec.set(vec); + } + } + + public MovingObjectPosition rayTraceCuboid(Vector3 start, Vector3 end, Cuboid6 cuboid) { + s_dist = Double.MAX_VALUE; + s_side = -1; + + for (int i = 0; i < 6; i++) traceSide(i, start, end, cuboid); + + if (s_side < 0) return null; + + MovingObjectPosition mop = new MovingObjectPosition(0, 0, 0, s_side, s_vec.toVec3D()); + mop.typeOfHit = null; + return mop; + } + + public MovingObjectPosition rayTraceCuboids(Vector3 start, Vector3 end, List cuboids) { + double c_dist = Double.MAX_VALUE; + MovingObjectPosition c_hit = null; + + for (IndexedCuboid6 cuboid : cuboids) { + MovingObjectPosition mop = rayTraceCuboid(start, end, cuboid); + if (mop != null && s_dist < c_dist) { + mop = new ExtendedMOP(mop, cuboid.data, s_dist); + c_dist = s_dist; + c_hit = mop; + c_cuboid = cuboid; + } + } + + return c_hit; + } + + public MovingObjectPosition rayTraceCuboids(Vector3 start, Vector3 end, List cuboids, + BlockCoord pos, Block block) { + MovingObjectPosition mop = rayTraceCuboids(start, end, cuboids); + if (mop != null) { + mop.typeOfHit = MovingObjectType.BLOCK; + mop.blockX = pos.x; + mop.blockY = pos.y; + mop.blockZ = pos.z; + if (block != null) c_cuboid.add(new Vector3(-pos.x, -pos.y, -pos.z)).setBlockBounds(block); + } + return mop; + } + + public static MovingObjectPosition retraceBlock(World world, EntityPlayer player, int x, int y, int z) { + Block block = world.getBlock(x, y, z); + + Vec3 headVec = getCorrectedHeadVec(player); + Vec3 lookVec = player.getLook(1.0F); + double reach = getBlockReachDistance(player); + Vec3 endVec = headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach); + return block.collisionRayTrace(world, x, y, z, headVec, endVec); + } + + private static double getBlockReachDistance_server(EntityPlayerMP player) { + return player.theItemInWorldManager.getBlockReachDistance(); + } + + @SideOnly(Side.CLIENT) + private static double getBlockReachDistance_client() { + return Minecraft.getMinecraft().playerController.getBlockReachDistance(); + } + + public static MovingObjectPosition reTrace(World world, EntityPlayer player) { + return reTrace(world, player, getBlockReachDistance(player)); + } + + public static MovingObjectPosition reTrace(World world, EntityPlayer player, double reach) { + Vec3 headVec = getCorrectedHeadVec(player); + Vec3 lookVec = player.getLook(1); + Vec3 endVec = headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach); + return world.func_147447_a(headVec, endVec, true, false, true); + } + + public static Vec3 getCorrectedHeadVec(EntityPlayer player) { + Vec3 v = Vec3.createVectorHelper(player.posX, player.posY, player.posZ); + if (player.worldObj.isRemote) { + v.yCoord += player.getEyeHeight() - player.getDefaultEyeHeight(); // compatibility with eye height changing + // mods + } else { + v.yCoord += player.getEyeHeight(); + if (player instanceof EntityPlayerMP && player.isSneaking()) v.yCoord -= 0.08; + } + return v; + } + + public static Vec3 getStartVec(EntityPlayer player) { + return getCorrectedHeadVec(player); + } + + public static double getBlockReachDistance(EntityPlayer player) { + return player.worldObj.isRemote ? getBlockReachDistance_client() + : player instanceof EntityPlayerMP ? getBlockReachDistance_server((EntityPlayerMP) player) : 5D; + } + + public static Vec3 getEndVec(EntityPlayer player) { + Vec3 headVec = getCorrectedHeadVec(player); + Vec3 lookVec = player.getLook(1.0F); + double reach = getBlockReachDistance(player); + return headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach); + } +} diff --git a/src/main/java/codechicken/lib/render/BlockRenderer.java b/src/main/java/codechicken/lib/render/BlockRenderer.java new file mode 100644 index 0000000..212f8f4 --- /dev/null +++ b/src/main/java/codechicken/lib/render/BlockRenderer.java @@ -0,0 +1,204 @@ +package codechicken.lib.render; + +import codechicken.lib.lighting.LC; +import codechicken.lib.render.CCRenderState.VertexAttribute; +import codechicken.lib.vec.Cuboid6; + +public class BlockRenderer { + + public static class BlockFace implements CCRenderState.IVertexSource { + + public Vertex5[] verts = new Vertex5[] { new Vertex5(), new Vertex5(), new Vertex5(), new Vertex5() }; + public LC[] lightCoords = new LC[] { new LC(), new LC(), new LC(), new LC() }; + public boolean lcComputed = false; + public int side; + + @Override + public Vertex5[] getVertices() { + return verts; + } + + @Override + public T getAttributes(CCRenderState.VertexAttribute attr) { + return attr == CCRenderState.lightCoordAttrib() && lcComputed ? (T) lightCoords : null; + } + + @Override + public boolean hasAttribute(CCRenderState.VertexAttribute attr) { + return attr == CCRenderState.sideAttrib() || attr == CCRenderState.lightCoordAttrib() && lcComputed; + } + + @Override + public void prepareVertex(CCRenderState state) { + state.side = side; + } + + public BlockFace computeLightCoords() { + if (!lcComputed) { + for (int i = 0; i < 4; i++) lightCoords[i].compute(verts[i].vec, side); + lcComputed = true; + } + return this; + } + + public BlockFace loadCuboidFace(Cuboid6 c, int side) { + double x1 = c.min.x; + double x2 = c.max.x; + double y1 = c.min.y; + double y2 = c.max.y; + double z1 = c.min.z; + double z2 = c.max.z; + double u1; + double u2; + double v1; + double v2; + this.side = side; + lcComputed = false; + + switch (side) { + case 0: + u1 = x1; + v1 = z1; + u2 = x2; + v2 = z2; + verts[0].set(x1, y1, z2, u1, v2, 0); + verts[1].set(x1, y1, z1, u1, v1, 0); + verts[2].set(x2, y1, z1, u2, v1, 0); + verts[3].set(x2, y1, z2, u2, v2, 0); + break; + case 1: + u1 = x1; + v1 = z1; + u2 = x2; + v2 = z2; + verts[0].set(x2, y2, z2, u2, v2, 1); + verts[1].set(x2, y2, z1, u2, v1, 1); + verts[2].set(x1, y2, z1, u1, v1, 1); + verts[3].set(x1, y2, z2, u1, v2, 1); + break; + case 2: + u1 = 1 - x1; + v1 = 1 - y2; + u2 = 1 - x2; + v2 = 1 - y1; + verts[0].set(x1, y1, z1, u1, v2, 2); + verts[1].set(x1, y2, z1, u1, v1, 2); + verts[2].set(x2, y2, z1, u2, v1, 2); + verts[3].set(x2, y1, z1, u2, v2, 2); + break; + case 3: + u1 = x1; + v1 = 1 - y2; + u2 = x2; + v2 = 1 - y1; + verts[0].set(x2, y1, z2, u2, v2, 3); + verts[1].set(x2, y2, z2, u2, v1, 3); + verts[2].set(x1, y2, z2, u1, v1, 3); + verts[3].set(x1, y1, z2, u1, v2, 3); + break; + case 4: + u1 = z1; + v1 = 1 - y2; + u2 = z2; + v2 = 1 - y1; + verts[0].set(x1, y1, z2, u2, v2, 4); + verts[1].set(x1, y2, z2, u2, v1, 4); + verts[2].set(x1, y2, z1, u1, v1, 4); + verts[3].set(x1, y1, z1, u1, v2, 4); + break; + case 5: + u1 = 1 - z1; + v1 = 1 - y2; + u2 = 1 - z2; + v2 = 1 - y1; + verts[0].set(x2, y1, z1, u1, v2, 5); + verts[1].set(x2, y2, z1, u1, v1, 5); + verts[2].set(x2, y2, z2, u2, v1, 5); + verts[3].set(x2, y1, z2, u2, v2, 5); + } + return this; + } + } + + public static class FullBlock implements CCRenderState.IVertexSource { + + public Vertex5[] verts = CCModel.quadModel(24).generateBlock(0, Cuboid6.full).verts; + public LC[] lightCoords = new LC[24]; + + public FullBlock() { + for (int i = 0; i < 24; i++) lightCoords[i] = new LC().compute(verts[i].vec, i / 4); + } + + @Override + public Vertex5[] getVertices() { + return verts; + } + + @Override + public T getAttributes(VertexAttribute attr) { + return attr == CCRenderState.lightCoordAttrib() ? (T) lightCoords : null; + } + + @Override + public boolean hasAttribute(VertexAttribute attr) { + return attr == CCRenderState.sideAttrib() || attr == CCRenderState.lightCoordAttrib(); + } + + @Override + public void prepareVertex(CCRenderState state) { + state.side = state.vertexIndex >> 2; + } + } + + public static FullBlock fullBlock = new FullBlock(); + + // public static void renderFullBlock(int sideMask) { + public static void renderFullBlock(CCRenderState state, int sideMask) { + state.setModelInstance(fullBlock); + renderFaces(state, sideMask); + } + + public static void renderFullBlock(int sideMask) { + renderFullBlock(CCRenderState.instance(), sideMask); + } + + /** + * Renders faces of a block-like model based on a sideMask. Eg for side 2, verts 8-11 will be rendered + * + * @param sideMask A mask of faces not to render + */ + // public static void renderFaces(int sideMask) { + public static void renderFaces(CCRenderState state, int sideMask) { + if (sideMask == 0x3F) return; + for (int s = 0; s < 6; s++) if ((sideMask & 1 << s) == 0) { + state.setVertexRangeInstance(s * 4, (s + 1) * 4); + state.renderInstance(); + } + } + + public static void renderFaces(int sideMask) { + renderFaces(CCRenderState.instance(), sideMask); + } + + private static final BlockFace face = new BlockFace(); + + /** + * Renders faces of a cuboid with texture coordinates mapped to match a standard minecraft block + * + * @param bounds The bounding cuboid to render + * @param sideMask A mask of faces not to render + */ + public static void renderCuboid(CCRenderState state, Cuboid6 bounds, int sideMask) { + if (sideMask == 0x3F) return; + + state.setModelInstance(face); + for (int s = 0; s < 6; s++) if ((sideMask & 1 << s) == 0) { + face.loadCuboidFace(bounds, s); + state.renderInstance(); + } + } + + public static void renderCuboid(Cuboid6 bounds, int sideMask) { + renderCuboid(CCRenderState.instance(), bounds, sideMask); + } +} diff --git a/src/main/java/codechicken/lib/render/CCModel.java b/src/main/java/codechicken/lib/render/CCModel.java new file mode 100644 index 0000000..62cfd99 --- /dev/null +++ b/src/main/java/codechicken/lib/render/CCModel.java @@ -0,0 +1,1024 @@ +package codechicken.lib.render; + +import static codechicken.lib.vec.Rotation.sideRotations; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.lighting.LC; +import codechicken.lib.lighting.LightModel; +import codechicken.lib.render.uv.UV; +import codechicken.lib.render.uv.UVTransformation; +import codechicken.lib.render.uv.UVTranslation; +import codechicken.lib.util.Copyable; +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.RedundantTransformation; +import codechicken.lib.vec.Transformation; +import codechicken.lib.vec.TransformationList; +import codechicken.lib.vec.Vector3; + +@SuppressWarnings("ForLoopReplaceableByForEach") +public class CCModel implements CCRenderState.IVertexSource, Copyable { + + private static class PositionNormalEntry { + + public Vector3 pos; + public LinkedList normals = new LinkedList(); + + public PositionNormalEntry(Vector3 position) { + pos = position; + } + + public boolean positionEqual(Vector3 v) { + return pos.x == v.x && pos.y == v.y && pos.z == v.z; + } + + public PositionNormalEntry addNormal(Vector3 normal) { + normals.add(normal); + return this; + } + } + + public final int vertexMode; + public final int vp; + public Vertex5[] verts; + public ArrayList attributes = new ArrayList(); + + protected CCModel(int vertexMode) { + if (vertexMode != 7 && vertexMode != 4) + throw new IllegalArgumentException("Models must be GL_QUADS or GL_TRIANGLES"); + + this.vertexMode = vertexMode; + vp = vertexMode == 7 ? 4 : 3; + } + + public Vector3[] normals() { + return getAttributes(CCRenderState.normalAttrib()); + } + + @Override + public Vertex5[] getVertices() { + return verts; + } + + @Override + public T getAttributes(CCRenderState.VertexAttribute attr) { + if (attr.attributeIndex < attributes.size()) return (T) attributes.get(attr.attributeIndex); + + return null; + } + + @Override + public boolean hasAttribute(CCRenderState.VertexAttribute attrib) { + return attrib.attributeIndex < attributes.size() && attributes.get(attrib.attributeIndex) != null; + } + + @Override + public void prepareVertex(CCRenderState state) {} + + public T getOrAllocate(CCRenderState.VertexAttribute attrib) { + T array = getAttributes(attrib); + if (array == null) { + while (attributes.size() <= attrib.attributeIndex) attributes.add(null); + attributes.set(attrib.attributeIndex, array = attrib.newArray(verts.length)); + } + return array; + } + + /** + * Each pixel corresponds to one unit of position when generating the model + * + * @param i Vertex index to start generating at + * @param x1 The minX bound of the box + * @param y1 The minY bound of the box + * @param z1 The minZ bound of the box + * @param w The width of the box + * @param h The height of the box + * @param d The depth of the box + * @param tx The distance of the top left corner of the texture map from the left in pixels + * @param ty The distance of the top left corner of the texture map from the top in pixels + * @param tw The width of the texture in pixels + * @param th The height of the texture in pixels + * @param f The scale of the model, pixels per block, normally 16 + * @return The generated model + */ + public CCModel generateBox(int i, double x1, double y1, double z1, double w, double h, double d, double tx, + double ty, double tw, double th, double f) { + double u1, v1, u2, v2; + double x2 = x1 + w; + double y2 = y1 + h; + double z2 = z1 + d; + x1 /= f; + x2 /= f; + y1 /= f; + y2 /= f; + z1 /= f; + z2 /= f; + + // bottom face + u1 = (tx + d + w) / tw; + v1 = (ty + d) / th; + u2 = (tx + d * 2 + w) / tw; + v2 = ty / th; + verts[i++] = new Vertex5(x1, y1, z2, u1, v2); + verts[i++] = new Vertex5(x1, y1, z1, u1, v1); + verts[i++] = new Vertex5(x2, y1, z1, u2, v1); + verts[i++] = new Vertex5(x2, y1, z2, u2, v2); + + // top face + u1 = (tx + d) / tw; + v1 = (ty + d) / th; + u2 = (tx + d + w) / tw; + v2 = ty / th; + verts[i++] = new Vertex5(x2, y2, z2, u2, v2); + verts[i++] = new Vertex5(x2, y2, z1, u2, v1); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1); + verts[i++] = new Vertex5(x1, y2, z2, u1, v2); + + // front face + u1 = (tx + d + w) / tw; + v1 = (ty + d) / th; + u2 = (tx + d) / tw; + v2 = (ty + d + h) / th; + verts[i++] = new Vertex5(x1, y2, z1, u2, v1); + verts[i++] = new Vertex5(x2, y2, z1, u1, v1); + verts[i++] = new Vertex5(x2, y1, z1, u1, v2); + verts[i++] = new Vertex5(x1, y1, z1, u2, v2); + + // back face + u1 = (tx + d * 2 + w * 2) / tw; + v1 = (ty + d) / th; + u2 = (tx + d * 2 + w) / tw; + v2 = (ty + d + h) / th; + verts[i++] = new Vertex5(x1, y2, z2, u1, v1); + verts[i++] = new Vertex5(x1, y1, z2, u1, v2); + verts[i++] = new Vertex5(x2, y1, z2, u2, v2); + verts[i++] = new Vertex5(x2, y2, z2, u2, v1); + + // left face + u1 = (tx + d) / tw; + v1 = (ty + d) / th; + u2 = (tx) / tw; + v2 = (ty + d + h) / th; + verts[i++] = new Vertex5(x1, y2, z2, u2, v1); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1); + verts[i++] = new Vertex5(x1, y1, z1, u1, v2); + verts[i++] = new Vertex5(x1, y1, z2, u2, v2); + + // right face + u1 = (tx + d * 2 + w) / tw; + v1 = (ty + d) / th; + u2 = (tx + d + w) / tw; + v2 = (ty + d + h) / th; + verts[i++] = new Vertex5(x2, y1, z2, u1, v2); + verts[i++] = new Vertex5(x2, y1, z1, u2, v2); + verts[i++] = new Vertex5(x2, y2, z1, u2, v1); + verts[i++] = new Vertex5(x2, y2, z2, u1, v1); + + return this; + } + + /** + * Generates a box, uv mapped to be the same as a minecraft block with the same bounds + * + * @param i The vertex index to start generating at + * @param bounds The bounds of the block, 0 to 1 + * @return The generated model. When rendering an icon will need to be supplied for the UV transformation. + */ + public CCModel generateBlock(int i, Cuboid6 bounds) { + return generateBlock(i, bounds, 0); + } + + public CCModel generateBlock(int i, Cuboid6 bounds, int mask) { + return generateBlock( + i, + bounds.min.x, + bounds.min.y, + bounds.min.z, + bounds.max.x, + bounds.max.y, + bounds.max.z, + mask); + } + + public CCModel generateBlock(int i, double x1, double y1, double z1, double x2, double y2, double z2) { + return generateBlock(i, x1, y1, z1, x2, y2, z2, 0); + } + + /** + * Generates a box, uv mapped to be the same as a minecraft block with the same bounds + * + * @param i The vertex index to start generating at + * @param x1 minX + * @param y1 minY + * @param z1 minZ + * @param x2 maxX + * @param y2 maxY + * @param z2 maxZ + * @param mask A bitmask of sides NOT to generate. I high bit at index s means side s will not be generated + * @return The generated model. When rendering an icon will need to be supplied for the UV transformation. + */ + public CCModel generateBlock(int i, double x1, double y1, double z1, double x2, double y2, double z2, int mask) { + double u1, v1, u2, v2; + + if ((mask & 1) == 0) { // bottom face + u1 = x1; + v1 = z1; + u2 = x2; + v2 = z2; + verts[i++] = new Vertex5(x1, y1, z2, u1, v2, 0); + verts[i++] = new Vertex5(x1, y1, z1, u1, v1, 0); + verts[i++] = new Vertex5(x2, y1, z1, u2, v1, 0); + verts[i++] = new Vertex5(x2, y1, z2, u2, v2, 0); + } + + if ((mask & 2) == 0) { // top face + u1 = x1; + v1 = z1; + u2 = x2; + v2 = z2; + verts[i++] = new Vertex5(x2, y2, z2, u2, v2, 1); + verts[i++] = new Vertex5(x2, y2, z1, u2, v1, 1); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1, 1); + verts[i++] = new Vertex5(x1, y2, z2, u1, v2, 1); + } + + if ((mask & 4) == 0) { // east face + u1 = 1 - x1; + v1 = 1 - y2; + u2 = 1 - x2; + v2 = 1 - y1; + verts[i++] = new Vertex5(x1, y1, z1, u1, v2, 2); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1, 2); + verts[i++] = new Vertex5(x2, y2, z1, u2, v1, 2); + verts[i++] = new Vertex5(x2, y1, z1, u2, v2, 2); + } + + if ((mask & 8) == 0) { // west face + u1 = x1; + v1 = 1 - y2; + u2 = x2; + v2 = 1 - y1; + verts[i++] = new Vertex5(x2, y1, z2, u2, v2, 3); + verts[i++] = new Vertex5(x2, y2, z2, u2, v1, 3); + verts[i++] = new Vertex5(x1, y2, z2, u1, v1, 3); + verts[i++] = new Vertex5(x1, y1, z2, u1, v2, 3); + } + + if ((mask & 0x10) == 0) { // north face + u1 = z1; + v1 = 1 - y2; + u2 = z2; + v2 = 1 - y1; + verts[i++] = new Vertex5(x1, y1, z2, u2, v2, 4); + verts[i++] = new Vertex5(x1, y2, z2, u2, v1, 4); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1, 4); + verts[i++] = new Vertex5(x1, y1, z1, u1, v2, 4); + } + + if ((mask & 0x20) == 0) { // south face + u1 = 1 - z1; + v1 = 1 - y2; + u2 = 1 - z2; + v2 = 1 - y1; + verts[i++] = new Vertex5(x2, y1, z1, u1, v2, 5); + verts[i++] = new Vertex5(x2, y2, z1, u1, v1, 5); + verts[i++] = new Vertex5(x2, y2, z2, u2, v1, 5); + verts[i++] = new Vertex5(x2, y1, z2, u2, v2, 5); + } + + return this; + } + + public CCModel computeNormals() { + return computeNormals(0, verts.length); + } + + /** + * Computes the normals of all faces in the model. Uses the cross product of the vectors along 2 sides of the face + * + * @param start The first vertex to generate normals for + * @param length The number of vertices to generate normals for. Note this must be a multiple of 3 for triangles or + * 4 for quads + * @return The model + */ + public CCModel computeNormals(int start, int length) { + if (length % vp != 0 || start % vp != 0) + throw new IllegalArgumentException("Cannot generate normals across polygons"); + + Vector3[] normals = getOrAllocate(CCRenderState.normalAttrib()); + for (int k = 0; k < length; k += vp) { + int i = k + start; + Vector3 diff1 = verts[i + 1].vec.copy().subtract(verts[i].vec); + Vector3 diff2 = verts[i + vp - 1].vec.copy().subtract(verts[i].vec); + normals[i] = diff1.crossProduct(diff2).normalize(); + for (int d = 1; d < vp; d++) normals[i + d] = normals[i].copy(); + } + + return this; + } + + /** + * Computes lighting using the normals add a light model If the model is rotated, the lighting will no longer be + * valid + * + * @return The model + */ + public CCModel computeLighting(LightModel light) { + Vector3[] normals = normals(); + int[] colours = getAttributes(CCRenderState.lightingAttrib()); + if (colours == null) { + colours = getOrAllocate(CCRenderState.lightingAttrib()); + Arrays.fill(colours, -1); + } + for (int k = 0; k < verts.length; k++) colours[k] = light.apply(colours[k], normals[k]); + return this; + } + + public CCModel setColour(int c) { + int[] colours = getOrAllocate(CCRenderState.colourAttrib()); + Arrays.fill(colours, c); + return this; + } + + /** + * Computes the minecraft lighting coordinates for use with a LightMatrix + * + * @return The model + */ + public CCModel computeLightCoords() { + LC[] lcs = getOrAllocate(CCRenderState.lightCoordAttrib()); + Vector3[] normals = normals(); + for (int i = 0; i < verts.length; i++) lcs[i] = new LC().compute(verts[i].vec, normals[i]); + return this; + } + + /** + * Averages all normals at the same position to produce a smooth lighting effect. + * + * @return The model + */ + public CCModel smoothNormals() { + ArrayList map = new ArrayList(); + Vector3[] normals = normals(); + nextvert: for (int k = 0; k < verts.length; k++) { + Vector3 vec = verts[k].vec; + for (PositionNormalEntry e : map) if (e.positionEqual(vec)) { + e.addNormal(normals[k]); + continue nextvert; + } + + map.add(new PositionNormalEntry(vec).addNormal(normals[k])); + } + + for (PositionNormalEntry e : map) { + if (e.normals.size() <= 1) continue; + + Vector3 new_n = new Vector3(); + for (Vector3 n : e.normals) new_n.add(n); + + new_n.normalize(); + for (Vector3 n : e.normals) n.set(new_n); + } + + return this; + } + + public CCModel apply(Transformation t) { + for (int k = 0; k < verts.length; k++) verts[k].apply(t); + + Vector3[] normals = normals(); + if (normals != null) for (int k = 0; k < normals.length; k++) t.applyN(normals[k]); + + return this; + } + + public CCModel apply(UVTransformation uvt) { + for (int k = 0; k < verts.length; k++) verts[k].apply(uvt); + + return this; + } + + public CCModel expand(int extraVerts) { + int newLen = verts.length + extraVerts; + verts = Arrays.copyOf(verts, newLen); + for (int i = 0; i < attributes.size(); i++) if (attributes.get(i) != null) attributes.set( + i, + CCRenderState.copyOf( + (CCRenderState.VertexAttribute) CCRenderState.getAttribute(i), + attributes.get(i), + newLen)); + + return this; + } + + public void render(CCRenderState state, double x, double y, double z, double u, double v) { + render(state, new Vector3(x, y, z).translation(), new UVTranslation(u, v)); + } + + public void render(double x, double y, double z, double u, double v) { + render(CCRenderState.instance(), new Vector3(x, y, z).translation(), new UVTranslation(u, v)); + } + + public void render(CCRenderState state, double x, double y, double z, UVTransformation u) { + render(state, new Vector3(x, y, z).translation(), u); + } + + public void render(double x, double y, double z, UVTransformation u) { + render(CCRenderState.instance(), new Vector3(x, y, z).translation(), u); + } + + public void render(CCRenderState state, Transformation t, double u, double v) { + render(state, t, new UVTranslation(u, v)); + } + + public void render(Transformation t, double u, double v) { + render(CCRenderState.instance(), t, new UVTranslation(u, v)); + } + + public void render(CCRenderState state, CCRenderState.IVertexOperation... ops) { + render(state, 0, verts.length, ops); + } + + public void render(CCRenderState.IVertexOperation... ops) { + render(CCRenderState.instance(), 0, verts.length, ops); + } + + /** + * Renders vertices start through start+length-1 of the model + * + * @param start The first vertex index to render + * @param end The vertex index to render until + * @param ops Operations to apply + */ + public void render(CCRenderState state, int start, int end, CCRenderState.IVertexOperation... ops) { + state.setPipelineInstance(this, start, end, ops); + state.renderInstance(); + } + + public void render(int start, int end, CCRenderState.IVertexOperation... ops) { + render(CCRenderState.instance(), start, end, ops); + } + + public static CCModel quadModel(int numVerts) { + return newModel(7, numVerts); + } + + public static CCModel triModel(int numVerts) { + return newModel(4, numVerts); + } + + public static CCModel newModel(int vertexMode, int numVerts) { + CCModel model = newModel(vertexMode); + model.verts = new Vertex5[numVerts]; + return model; + } + + public static CCModel newModel(int vertexMode) { + return new CCModel(vertexMode); + } + + public static double[] parseDoubles(String s, String token) { + String[] as = s.split(token); + double[] values = new double[as.length]; + for (int i = 0; i < as.length; i++) values[i] = Double.parseDouble(as[i]); + return values; + } + + public static void illegalAssert(boolean b, String err) { + if (!b) throw new IllegalArgumentException(err); + } + + public static void assertMatch(Matcher m, String s) { + m.reset(s); + illegalAssert(m.matches(), "Malformed line: " + s); + } + + private static final Pattern vertPattern = Pattern.compile("v(?: ([\\d\\.+-]+))+"); + private static final Pattern uvwPattern = Pattern.compile("vt(?: ([\\d\\.+-]+))+"); + private static final Pattern normalPattern = Pattern.compile("vn(?: ([\\d\\.+-]+))+"); + private static final Pattern polyPattern = Pattern.compile("f(?: ((?:\\d*)(?:/\\d*)?(?:/\\d*)?))+"); + public static final Matcher vertMatcher = vertPattern.matcher(""); + public static final Matcher uvwMatcher = uvwPattern.matcher(""); + public static final Matcher normalMatcher = normalPattern.matcher(""); + public static final Matcher polyMatcher = polyPattern.matcher(""); + + /** + * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file + * + * @param input An input stream to a obj file + * @param vertexMode The vertex mode to create the model for (GL_TRIANGLES or GL_QUADS) + * @param coordSystem The cooridnate system transformation to apply + * @return A map of group names to models + * @throws IOException + */ + public static Map parseObjModels(InputStream input, int vertexMode, Transformation coordSystem) + throws IOException { + if (coordSystem == null) coordSystem = new RedundantTransformation(); + int vp = vertexMode == 7 ? 4 : 3; + + HashMap modelMap = new HashMap(); + ArrayList verts = new ArrayList(); + ArrayList uvs = new ArrayList(); + ArrayList normals = new ArrayList(); + ArrayList polys = new ArrayList(); + String modelName = "unnamed"; + + BufferedReader reader = new BufferedReader(new InputStreamReader(input)); + + String line; + while ((line = reader.readLine()) != null) { + line = line.replaceAll("\\s+", " ").trim(); + if (line.startsWith("#") || line.length() == 0) continue; + + if (line.startsWith("v ")) { + assertMatch(vertMatcher, line); + double[] values = parseDoubles(line.substring(2), " "); + illegalAssert(values.length >= 3, "Vertices must have x, y and z components"); + Vector3 vert = new Vector3(values[0], values[1], values[2]); + coordSystem.apply(vert); + verts.add(vert); + continue; + } + if (line.startsWith("vt ")) { + assertMatch(uvwMatcher, line); + double[] values = parseDoubles(line.substring(3), " "); + illegalAssert(values.length >= 2, "Tex Coords must have u, and v components"); + uvs.add(new Vector3(values[0], 1 - values[1], 0)); + continue; + } + if (line.startsWith("vn ")) { + assertMatch(normalMatcher, line); + double[] values = parseDoubles(line.substring(3), " "); + illegalAssert(values.length >= 3, "Normals must have x, y and z components"); + Vector3 norm = new Vector3(values[0], values[1], values[2]).normalize(); + coordSystem.applyN(norm); + normals.add(norm); + continue; + } + if (line.startsWith("f ")) { + assertMatch(polyMatcher, line); + String[] av = line.substring(2).split(" "); + illegalAssert(av.length >= 3, "Polygons must have at least 3 vertices"); + int[][] polyVerts = new int[av.length][3]; + for (int i = 0; i < av.length; i++) { + String[] as = av[i].split("/"); + for (int p = 0; p < as.length; p++) + if (as[p].length() > 0) polyVerts[i][p] = Integer.parseInt(as[p]); + } + if (vp == 3) triangulate(polys, polyVerts); + else quadulate(polys, polyVerts); + } + if (line.startsWith("g ")) { + if (!polys.isEmpty()) { + modelMap.put(modelName, createModel(verts, uvs, normals, vertexMode, polys)); + polys.clear(); + } + modelName = line.substring(2); + } + } + + if (!polys.isEmpty()) modelMap.put(modelName, createModel(verts, uvs, normals, vertexMode, polys)); + + return modelMap; + } + + public static void triangulate(List polys, int[][] polyVerts) { + for (int i = 2; i < polyVerts.length; i++) { + polys.add(polyVerts[0]); + polys.add(polyVerts[i]); + polys.add(polyVerts[i - 1]); + } + } + + public static void quadulate(List polys, int[][] polyVerts) { + if (polyVerts.length == 4) { + polys.add(polyVerts[0]); + polys.add(polyVerts[3]); + polys.add(polyVerts[2]); + polys.add(polyVerts[1]); + } else { + for (int i = 2; i < polyVerts.length; i++) { + polys.add(polyVerts[0]); + polys.add(polyVerts[i]); + polys.add(polyVerts[i - 1]); + polys.add(polyVerts[i - 1]); + } + } + } + + /** + * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file + * + * @param res The resource for the obj file + * @return A map of group names to models + */ + public static Map parseObjModels(ResourceLocation res) { + return parseObjModels(res, 4, null); + } + + /** + * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file + * + * @param res The resource for the obj file + * @param coordSystem The cooridnate system transformation to apply + * @return A map of group names to models + */ + public static Map parseObjModels(ResourceLocation res, Transformation coordSystem) { + try { + return parseObjModels( + Minecraft.getMinecraft().getResourceManager().getResource(res).getInputStream(), + 4, + coordSystem); + } catch (IOException e) { + throw new RuntimeException("failed to load model: " + res, e); + } + } + + /** + * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file + * + * @param res The resource for the obj file + * @param vertexMode The vertex mode to create the model for (GL_TRIANGLES or GL_QUADS) + * @param coordSystem The cooridnate system transformation to apply + * @return A map of group names to models + */ + public static Map parseObjModels(ResourceLocation res, int vertexMode, + Transformation coordSystem) { + try { + return parseObjModels( + Minecraft.getMinecraft().getResourceManager().getResource(res).getInputStream(), + vertexMode, + coordSystem); + } catch (Exception e) { + throw new RuntimeException("failed to load model: " + res, e); + } + } + + public static CCModel createModel(List verts, List uvs, List normals, int vertexMode, + List polys) { + int vp = vertexMode == 7 ? 4 : 3; + if (polys.size() < vp || polys.size() % vp != 0) + throw new IllegalArgumentException("Invalid number of vertices for model: " + polys.size()); + + boolean hasNormals = polys.get(0)[2] > 0; + CCModel model = CCModel.newModel(vertexMode, polys.size()); + if (hasNormals) model.getOrAllocate(CCRenderState.normalAttrib()); + + for (int i = 0; i < polys.size(); i++) { + int[] ai = polys.get(i); + Vector3 vert = verts.get(ai[0] - 1).copy(); + Vector3 uv = ai[1] <= 0 ? new Vector3() : uvs.get(ai[1] - 1).copy(); + if (ai[2] > 0 != hasNormals) throw new IllegalArgumentException("Normals are an all or nothing deal here."); + + model.verts[i] = new Vertex5(vert, uv.x, uv.y); + if (hasNormals) model.normals()[i] = normals.get(ai[2] - 1).copy(); + } + + return model; + } + + private static int addIndex(List list, T elem) { + int i = list.indexOf(elem) + 1; + if (i == 0) { + list.add(elem); + i = list.size(); + } + return i; + } + + private static String clean(double d) { + return d == (int) d ? Integer.toString((int) d) : Double.toString(d); + } + + public static void exportObj(Map models, PrintWriter p) { + List verts = new ArrayList(); + List uvs = new ArrayList(); + List normals = new ArrayList(); + List polys = new ArrayList(); + for (Map.Entry e : models.entrySet()) { + p.println("g " + e.getKey()); + CCModel m = e.getValue(); + + int vStart = verts.size(); + int uStart = uvs.size(); + int nStart = normals.size(); + boolean hasNormals = m.normals() != null; + polys.clear(); + + for (int i = 0; i < m.verts.length; i++) { + int[] ia = new int[hasNormals ? 3 : 2]; + ia[0] = addIndex(verts, m.verts[i].vec); + ia[1] = addIndex(uvs, m.verts[i].uv); + if (hasNormals) ia[2] = addIndex(normals, m.normals()[i]); + polys.add(ia); + } + + if (vStart < verts.size()) { + p.println(); + for (int i = vStart; i < verts.size(); i++) { + Vector3 v = verts.get(i); + p.format("v %s %s %s\n", clean(v.x), clean(v.y), clean(v.z)); + } + } + if (uStart < uvs.size()) { + p.println(); + for (int i = uStart; i < uvs.size(); i++) { + UV uv = uvs.get(i); + p.format("vt %s %s\n", clean(uv.u), clean(uv.v)); + } + } + if (nStart < normals.size()) { + p.println(); + for (int i = nStart; i < normals.size(); i++) { + Vector3 n = normals.get(i); + p.format("vn %s %s %s\n", clean(n.x), clean(n.y), clean(n.z)); + } + } + + p.println(); + for (int i = 0; i < polys.size(); i++) { + if (i % m.vp == 0) p.format("f"); + int[] ia = polys.get(i); + if (hasNormals) p.format(" %d/%d/%d", ia[0], ia[1], ia[2]); + else p.format(" %d/%d", ia[0], ia[1]); + if (i % m.vp == m.vp - 1) p.println(); + } + } + } + + /** + * Brings the UV coordinates of each face closer to the center UV by d. Useful for fixing texture seams + */ + public CCModel shrinkUVs(double d) { + for (int k = 0; k < verts.length; k += vp) { + UV uv = new UV(); + for (int i = 0; i < vp; i++) { + uv.add(verts[k + i].uv); + } + uv.multiply(1D / vp); + for (int i = 0; i < vp; i++) { + Vertex5 vert = verts[k + i]; + vert.uv.u += vert.uv.u < uv.u ? d : -d; + vert.uv.v += vert.uv.v < uv.v ? d : -d; + } + } + return this; + } + + /** + * @param side1 The side of this model + * @param side2 The side of the new model + * @param point The point to rotate around + * @return A copy of this model rotated to the appropriate side + */ + public CCModel sidedCopy(int side1, int side2, Vector3 point) { + return copy().apply(new TransformationList(sideRotations[side1].inverse(), sideRotations[side2]).at(point)); + } + + /** + * Copies length vertices and normals + */ + public static void copy(CCModel src, int srcpos, CCModel dst, int destpos, int length) { + for (int k = 0; k < length; k++) dst.verts[destpos + k] = src.verts[srcpos + k].copy(); + + for (int i = 0; i < src.attributes.size(); i++) if (src.attributes.get(i) != null) CCRenderState.arrayCopy( + src.attributes.get(i), + srcpos, + dst.getOrAllocate(CCRenderState.getAttribute(i)), + destpos, + length); + } + + /** + * Generate models rotated to the other 5 sides of the block + * + * @param models An array of 6 models + * @param side The side of this model + * @param point The rotation point + */ + public static void generateSidedModels(CCModel[] models, int side, Vector3 point) { + for (int s = 0; s < 6; s++) { + if (s == side) continue; + + models[s] = models[side].sidedCopy(side, s, point); + } + } + + /** + * Generate models rotated to the other 3 horizontal of the block + * + * @param models An array of 4 models + * @param side The side of this model + * @param point The rotation point + */ + public static void generateSidedModelsH(CCModel[] models, int side, Vector3 point) { + for (int s = 2; s < 6; s++) { + if (s == side) continue; + + models[s] = models[side].sidedCopy(side, s, point); + } + } + + public CCModel backfacedCopy() { + return generateBackface(this, 0, copy(), 0, verts.length); + } + + /** + * Generates copies of faces with clockwise vertices + * + * @return The model + */ + public static CCModel generateBackface(CCModel src, int srcpos, CCModel dst, int destpos, int length) { + int vp = src.vp; + if (srcpos % vp != 0 || destpos % vp != 0 || length % vp != 0) + throw new IllegalArgumentException("Vertices do not align with polygons"); + + int[][] o = new int[][] { { 0, 0 }, { 1, vp - 1 }, { 2, vp - 2 }, { 3, vp - 3 } }; + for (int i = 0; i < length; i++) { + int b = (i / vp) * vp; + int d = i % vp; + int di = destpos + b + o[d][1]; + int si = srcpos + b + o[d][0]; + dst.verts[di] = src.verts[si].copy(); + for (int a = 0; a < src.attributes.size(); a++) if (src.attributes.get(a) != null) CCRenderState + .arrayCopy(src.attributes.get(a), si, dst.getOrAllocate(CCRenderState.getAttribute(a)), di, 1); + + if (dst.normals() != null && dst.normals()[di] != null) dst.normals()[di].negate(); + } + return dst; + } + + /** + * Generates sided copies of vertices into this model. Assumes that your model has been generated at vertex + * side*(numVerts/6) + */ + public CCModel generateSidedParts(int side, Vector3 point) { + if (verts.length % (6 * vp) != 0) + throw new IllegalArgumentException("Invalid number of vertices for sided part generation"); + int length = verts.length / 6; + + for (int s = 0; s < 6; s++) { + if (s == side) continue; + + generateSidedPart(side, s, point, length * side, length * s, length); + } + + return this; + } + + /** + * Generates sided copies of vertices into this model. Assumes that your model has been generated at vertex + * side*(numVerts/4) + */ + public CCModel generateSidedPartsH(int side, Vector3 point) { + if (verts.length % (4 * vp) != 0) + throw new IllegalArgumentException("Invalid number of vertices for sided part generation"); + int length = verts.length / 4; + + for (int s = 2; s < 6; s++) { + if (s == side) continue; + + generateSidedPart(side, s, point, length * (side - 2), length * (s - 2), length); + } + + return this; + } + + /** + * Generates a sided copy of verts into this model + */ + public CCModel generateSidedPart(int side1, int side2, Vector3 point, int srcpos, int destpos, int length) { + return apply( + new TransformationList(sideRotations[side1].inverse(), sideRotations[side2]).at(point), + srcpos, + destpos, + length); + } + + /** + * Generates a rotated copy of verts into this model + */ + public CCModel apply(Transformation t, int srcpos, int destpos, int length) { + for (int k = 0; k < length; k++) { + verts[destpos + k] = verts[srcpos + k].copy(); + verts[destpos + k].vec.apply(t); + } + + Vector3[] normals = normals(); + if (normals != null) for (int k = 0; k < length; k++) { + normals[destpos + k] = normals[srcpos + k].copy(); + t.applyN(normals[destpos + k]); + } + + return this; + } + + public static CCModel combine(Collection models) { + if (models.isEmpty()) return null; + + int numVerts = 0; + int vertexMode = -1; + for (CCModel model : models) { + if (vertexMode == -1) vertexMode = model.vertexMode; + if (vertexMode != model.vertexMode) + throw new IllegalArgumentException("Cannot combine models with different vertex modes"); + + numVerts += model.verts.length; + } + + CCModel c_model = newModel(vertexMode, numVerts); + int i = 0; + for (CCModel model : models) { + copy(model, 0, c_model, i, model.verts.length); + i += model.verts.length; + } + + return c_model; + } + + public CCModel twoFacedCopy() { + CCModel model = newModel(vertexMode, verts.length * 2); + copy(this, 0, model, 0, verts.length); + return generateBackface(model, 0, model, verts.length, verts.length); + } + + public CCModel copy() { + CCModel model = newModel(vertexMode, verts.length); + copy(this, 0, model, 0, verts.length); + return model; + } + + /** + * @return The average of all vertices, for bones. + */ + public Vector3 collapse() { + Vector3 v = new Vector3(); + for (Vertex5 vert : verts) v.add(vert.vec); + v.multiply(1 / (double) verts.length); + return v; + } + + public CCModel zOffset(Cuboid6 offsets) { + for (int k = 0; k < verts.length; k++) { + Vertex5 vert = verts[k]; + Vector3 normal = normals()[k]; + switch (findSide(normal)) { + case 0: + vert.vec.y += offsets.min.y; + break; + case 1: + vert.vec.y += offsets.max.y; + break; + case 2: + vert.vec.z += offsets.min.z; + break; + case 3: + vert.vec.z += offsets.max.z; + break; + case 4: + vert.vec.x += offsets.min.x; + break; + case 5: + vert.vec.x += offsets.max.x; + break; + } + } + return this; + } + + public static int findSide(Vector3 normal) { + if (normal.y <= -0.99) return 0; + if (normal.y >= 0.99) return 1; + if (normal.z <= -0.99) return 2; + if (normal.z >= 0.99) return 3; + if (normal.x <= -0.99) return 4; + if (normal.x >= 0.99) return 5; + return -1; + } + + /** + * @return A Cuboid6 containing all the verts in this model + */ + public Cuboid6 bounds() { + Vector3 vec1 = verts[0].vec; + Cuboid6 c = new Cuboid6(vec1.copy(), vec1.copy()); + for (int i = 1; i < verts.length; i++) c.enclose(verts[i].vec); + return c; + } +} diff --git a/src/main/java/codechicken/lib/render/CCModelLibrary.java b/src/main/java/codechicken/lib/render/CCModelLibrary.java new file mode 100644 index 0000000..f2da368 --- /dev/null +++ b/src/main/java/codechicken/lib/render/CCModelLibrary.java @@ -0,0 +1,92 @@ +package codechicken.lib.render; + +import static codechicken.lib.math.MathHelper.phi; + +import codechicken.lib.vec.Matrix4; +import codechicken.lib.vec.Quat; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Vector3; + +public class CCModelLibrary { + + public static CCModel icosahedron4; + public static CCModel icosahedron7; + + private static int i; + + static { + generateIcosahedron(); + } + + private static void generateIcosahedron() { + Vector3[] verts = new Vector3[12]; + + verts[0] = new Vector3(-1, phi, 0); + verts[1] = new Vector3(1, phi, 0); + verts[2] = new Vector3(1, -phi, 0); + verts[3] = new Vector3(-1, -phi, 0); + + verts[4] = new Vector3(0, -1, phi); + verts[5] = new Vector3(0, 1, phi); + verts[6] = new Vector3(0, 1, -phi); + verts[7] = new Vector3(0, -1, -phi); + + verts[8] = new Vector3(phi, 0, -1); + verts[9] = new Vector3(phi, 0, 1); + verts[10] = new Vector3(-phi, 0, 1); + verts[11] = new Vector3(-phi, 0, -1); + + Quat quat = Quat.aroundAxis(0, 0, 1, Math.atan(1 / phi)); + for (Vector3 vec : verts) quat.rotate(vec); + + icosahedron4 = CCModel.newModel(4, 60); + icosahedron7 = CCModel.newModel(7, 80); + + i = 0; + // top + addIcosahedronTriangle(verts[1], 0.5, 0, verts[0], 0, 0.25, verts[5], 1, 0.25); + addIcosahedronTriangle(verts[1], 0.5, 0, verts[5], 0, 0.25, verts[9], 1, 0.25); + addIcosahedronTriangle(verts[1], 0.5, 0, verts[9], 0, 0.25, verts[8], 1, 0.25); + addIcosahedronTriangle(verts[1], 0.5, 0, verts[8], 0, 0.25, verts[6], 1, 0.25); + addIcosahedronTriangle(verts[1], 0.5, 0, verts[6], 0, 0.25, verts[0], 1, 0.25); + // centre 1vert top + addIcosahedronTriangle(verts[0], 0.5, 0.25, verts[11], 0, 0.75, verts[10], 1, 0.75); + addIcosahedronTriangle(verts[5], 0.5, 0.25, verts[10], 0, 0.75, verts[4], 1, 0.75); + addIcosahedronTriangle(verts[9], 0.5, 0.25, verts[4], 0, 0.75, verts[2], 1, 0.75); + addIcosahedronTriangle(verts[8], 0.5, 0.25, verts[2], 0, 0.75, verts[7], 1, 0.75); + addIcosahedronTriangle(verts[6], 0.5, 0.25, verts[7], 0, 0.75, verts[11], 1, 0.75); + // centre 1vert bottom + addIcosahedronTriangle(verts[2], 0.5, 0.75, verts[8], 0, 0.25, verts[9], 1, 0.25); + addIcosahedronTriangle(verts[7], 0.5, 0.75, verts[6], 0, 0.25, verts[8], 1, 0.25); + addIcosahedronTriangle(verts[11], 0.5, 0.75, verts[0], 0, 0.25, verts[6], 1, 0.25); + addIcosahedronTriangle(verts[10], 0.5, 0.75, verts[5], 0, 0.25, verts[0], 1, 0.25); + addIcosahedronTriangle(verts[4], 0.5, 0.75, verts[9], 0, 0.25, verts[5], 1, 0.25); + // bottom + addIcosahedronTriangle(verts[3], 0.5, 1, verts[2], 0, 0.75, verts[4], 1, 0.75); + addIcosahedronTriangle(verts[3], 0.5, 1, verts[7], 0, 0.75, verts[2], 1, 0.75); + addIcosahedronTriangle(verts[3], 0.5, 1, verts[11], 0, 0.75, verts[7], 1, 0.75); + addIcosahedronTriangle(verts[3], 0.5, 1, verts[10], 0, 0.75, verts[11], 1, 0.75); + addIcosahedronTriangle(verts[3], 0.5, 1, verts[4], 0, 0.75, verts[10], 1, 0.75); + + icosahedron4.computeNormals().smoothNormals(); + icosahedron7.computeNormals().smoothNormals(); + } + + private static void addIcosahedronTriangle(Vector3 vec1, double u1, double v1, Vector3 vec2, double u2, double v2, + Vector3 vec3, double u3, double v3) { + icosahedron4.verts[i * 3] = icosahedron7.verts[i * 4] = new Vertex5(vec1, u1, v1); + icosahedron4.verts[i * 3 + 1] = icosahedron7.verts[i * 4 + 1] = new Vertex5(vec2, u2, v2); + icosahedron4.verts[i * 3 + + 2] = icosahedron7.verts[i * 4 + 2] = icosahedron7.verts[i * 4 + 3] = new Vertex5(vec3, u3, v3); + i++; + } + + @Deprecated + public static Matrix4 getRenderMatrix(Vector3 position, Rotation rotation, double scale) { + return new Matrix4().translate(position).scale(scale).apply(rotation); + } + + public static Matrix4 getRenderMatrix(double posX, double posY, double posZ, Rotation rotation, double scale) { + return new Matrix4().translate(posX, posY, posZ).scale(scale).apply(rotation); + } +} diff --git a/src/main/java/codechicken/lib/render/CCRenderPipeline.java b/src/main/java/codechicken/lib/render/CCRenderPipeline.java new file mode 100644 index 0000000..7d54d2a --- /dev/null +++ b/src/main/java/codechicken/lib/render/CCRenderPipeline.java @@ -0,0 +1,142 @@ +package codechicken.lib.render; + +import java.util.ArrayList; +import java.util.Collections; + +import codechicken.lib.render.CCRenderState.IVertexOperation; +import codechicken.lib.render.CCRenderState.VertexAttribute; + +@SuppressWarnings("ForLoopReplaceableByForEach") +public class CCRenderPipeline { + + private final CCRenderState renderState; + private final PipelineBuilder builder; + + public CCRenderPipeline(CCRenderState renderState) { + this.renderState = renderState; + builder = new PipelineBuilder(renderState); + } + + public CCRenderPipeline() { + this(CCRenderState.instance()); + } + + public class PipelineBuilder { + + private final CCRenderState renderState; + + public PipelineBuilder(CCRenderState renderState) { + this.renderState = renderState; + } + + public PipelineBuilder() { + this(CCRenderState.instance()); + } + + public PipelineBuilder add(IVertexOperation op) { + ops.add(op); + return this; + } + + public PipelineBuilder add(IVertexOperation... ops) { + Collections.addAll(CCRenderPipeline.this.ops, ops); + return this; + } + + public void build() { + rebuild(); + } + + public void render() { + rebuild(); + renderState.renderInstance(); + } + } + + private class PipelineNode { + + public ArrayList deps = new ArrayList<>(); + public IVertexOperation op; + + public void add() { + if (op == null) return; + + for (int i = 0; i < deps.size(); i++) deps.get(i).add(); + deps.clear(); + sorted.add(op); + op = null; + } + } + + private final ArrayList attribs = new ArrayList<>(); + private final ArrayList ops = new ArrayList<>(); + private final ArrayList nodes = new ArrayList<>(); + private final ArrayList sorted = new ArrayList<>(); + private PipelineNode loading; + + public void setPipeline(IVertexOperation... ops) { + this.ops.clear(); + for (int i = 0; i < ops.length; i++) this.ops.add(ops[i]); + rebuild(); + } + + public void reset() { + ops.clear(); + unbuild(); + } + + private void unbuild() { + for (int i = 0; i < attribs.size(); i++) attribs.get(i).active = false; + attribs.clear(); + sorted.clear(); + } + + public void rebuild() { + if (ops.isEmpty() || this.renderState.model == null) return; + + // ensure enough nodes for all ops + while (nodes.size() < this.renderState.operationCount()) nodes.add(new PipelineNode()); + unbuild(); + + if (this.renderState.useNormals) addAttribute(this.renderState.normalAttrib); + if (this.renderState.useColour) addAttribute(this.renderState.colourAttrib); + if (this.renderState.computeLighting) addAttribute(this.renderState.lightingAttrib); + + for (int i = 0; i < ops.size(); i++) { + IVertexOperation op = ops.get(i); + loading = nodes.get(op.operationID()); + boolean loaded = op.load(renderState); + if (loaded) loading.op = op; + + if (op instanceof VertexAttribute) if (loaded) attribs.add((VertexAttribute) op); + else((VertexAttribute) op).active = false; + } + + for (int i = 0; i < nodes.size(); i++) nodes.get(i).add(); + } + + public void addRequirement(int opRef) { + loading.deps.add(nodes.get(opRef)); + } + + public void addDependency(VertexAttribute attrib) { + loading.deps.add(nodes.get(attrib.operationID())); + addAttribute(attrib); + } + + public void addAttribute(VertexAttribute attrib) { + if (!attrib.active) { + ops.add(attrib); + attrib.active = true; + } + } + + public void operate() { + for (int i = 0; i < sorted.size(); i++) sorted.get(i).operate(renderState); + } + + public PipelineBuilder builder() { + ops.clear(); + return builder; + } +} diff --git a/src/main/java/codechicken/lib/render/CCRenderState.java b/src/main/java/codechicken/lib/render/CCRenderState.java new file mode 100644 index 0000000..1eb74f1 --- /dev/null +++ b/src/main/java/codechicken/lib/render/CCRenderState.java @@ -0,0 +1,593 @@ +package codechicken.lib.render; + +import java.util.ArrayList; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.IBlockAccess; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.lighting.LC; +import codechicken.lib.lighting.LightMatrix; +import codechicken.lib.util.Copyable; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Transformation; +import codechicken.lib.vec.Vector3; + +/** + * The core of the CodeChickenLib render system. Where possible assign a local var of CCRenderState to avoid millions of + * calls to instance(); Uses a ThreadLocal system to assign each thread their own CCRenderState so we can use it in + * Multithreaded chunk batching. + *

+ * Backported from CCL - 1.16.x + */ + +public class CCRenderState { + + public final CCRenderPipeline pipeline; + + private static final ThreadLocal instances = ThreadLocal.withInitial(CCRenderState::new); + + private CCRenderState() { + pipeline = new CCRenderPipeline(this); + } + + public static CCRenderState instance() { + return instances.get(); + } + + private static int nextOperationIndex; + + public static int registerOperation() { + return nextOperationIndex++; + } + + public static int operationCount() { + return nextOperationIndex; + } + + /** + * Represents an operation to be run for each vertex that operates on and modifies the current state + */ + public interface IVertexOperation { + + /** + * Load any required references and add dependencies to the pipeline based on the current model (may be null) + * Return false if this operation is redundant in the pipeline with the given model + */ + default boolean load() { + // Existing code will will override this method so it shouldn't infinite loop + // Default it only for new state aware code + return load(CCRenderState.instance()); + } + + default boolean load(CCRenderState state) { + // New code will override this and not inplement load() so we shouldn't infinite loop + return load(); + } + + /** + * Perform the operation on the current render state + */ + default void operate() { + operate(CCRenderState.instance()); + } + + default void operate(CCRenderState state) { + operate(); + } + + /** + * Get the unique id representing this type of operation. Duplicate operation IDs within the pipeline may have + * unexpected results. ID shoulld be obtained from CCRenderState.registerOperation() and stored in a static + * variable + */ + int operationID(); + } + + private static ArrayList> vertexAttributes = new ArrayList<>(); + + private static int registerVertexAttribute(VertexAttribute attr) { + vertexAttributes.add(attr); + return vertexAttributes.size() - 1; + } + + public static VertexAttribute getAttribute(int index) { + return vertexAttributes.get(index); + } + + /** + * Management class for a vertex attrute such as colour, normal etc This class should handle the loading of the + * attrute from an array provided by IVertexSource.getAttributes or the computation of this attrute from others + * + * @param The array type for this attrute eg. int[], Vector3[] + */ + public abstract static class VertexAttribute implements IVertexOperation { + + public final int attributeIndex = registerVertexAttribute(this); + private final int operationIndex = registerOperation(); + /** + * Set to true when the attrute is part of the pipeline. Should only be managed by CCRenderState when + * constructing the pipeline + */ + public boolean active = false; + + /** + * Construct a new array for storage of vertex attrutes in a model + */ + public abstract T newArray(int length); + + @Override + public int operationID() { + return operationIndex; + } + } + + public static void arrayCopy(Object src, int srcPos, Object dst, int destPos, int length) { + System.arraycopy(src, srcPos, dst, destPos, length); + if (dst instanceof Copyable[]) { + Object[] oa = (Object[]) dst; + Copyable[] c = (Copyable[]) dst; + for (int i = destPos; i < destPos + length; i++) if (c[i] != null) oa[i] = c[i].copy(); + } + } + + public static T copyOf(VertexAttribute attr, T src, int length) { + T dst = attr.newArray(length); + arrayCopy(src, 0, dst, 0, ((Object[]) src).length); + return dst; + } + + public interface IVertexSource { + + Vertex5[] getVertices(); + + /** + * Gets an array of vertex attrutes + * + * @param attr The vertex attrute to get + * @param The attrute array type + * @return An array, or null if not computed + */ + T getAttributes(VertexAttribute attr); + + /** + * @return True if the specified attrute is provided by this model, either by returning an array from + * getAttributes or by setting the state in prepareVertex + */ + boolean hasAttribute(VertexAttribute attr); + + /** + * Callback to set CCRenderState for a vertex before the pipeline runs + */ + void prepareVertex(CCRenderState state); + + default void prepareVertex() { + prepareVertex(CCRenderState.instance()); + } + } + + public static VertexAttribute normalAttrib() { + return instances.get().normalAttrib; + } + + public static VertexAttribute colourAttrib() { + return instances.get().colourAttrib; + } + + public static VertexAttribute lightCoordAttrib() { + return instances.get().lightCoordAttrib; + } + + public static VertexAttribute sideAttrib() { + return instances.get().sideAttrib; + } + + public static VertexAttribute lightingAttrib() { + return instances.get().lightingAttrib; + } + + public VertexAttribute normalAttrib = new VertexAttribute<>() { + + private Vector3[] normalRef; + + @Override + public Vector3[] newArray(int length) { + return new Vector3[length]; + } + + @Override + public boolean load(CCRenderState state) { + normalRef = state.model.getAttributes(this); + if (state.model.hasAttribute(this)) return normalRef != null; + + if (state.model.hasAttribute(sideAttrib)) { + state.pipeline.addDependency(sideAttrib); + return true; + } + throw new IllegalStateException( + "Normals requested but neither normal or side attrutes are provided by the model"); + } + + @Override + public void operate(CCRenderState state) { + if (normalRef != null) state.setNormalInstance(normalRef[state.vertexIndex]); + else state.setNormalInstance(Rotation.axes[state.side]); + } + }; + public VertexAttribute colourAttrib = new VertexAttribute<>() { + + private int[] colourRef; + + @Override + public int[] newArray(int length) { + return new int[length]; + } + + @Override + public boolean load(CCRenderState state) { + colourRef = state.model.getAttributes(this); + return colourRef != null || !state.model.hasAttribute(this); + } + + @Override + public void operate(CCRenderState state) { + if (colourRef != null) + state.setColourInstance(ColourRGBA.multiply(state.baseColour, colourRef[state.vertexIndex])); + else state.setColourInstance(state.baseColour); + } + }; + public VertexAttribute lightingAttrib = new VertexAttribute<>() { + + private int[] colourRef; + + @Override + public int[] newArray(int length) { + return new int[length]; + } + + @Override + public boolean load(CCRenderState state) { + if (!state.computeLighting || !state.useColour || !state.model.hasAttribute(this)) return false; + + colourRef = state.model.getAttributes(this); + if (colourRef != null) { + state.pipeline.addDependency(colourAttrib); + return true; + } + return false; + } + + @Override + public void operate(CCRenderState state) { + state.setColourInstance(ColourRGBA.multiply(state.colour, colourRef[state.vertexIndex])); + } + }; + public VertexAttribute sideAttrib = new VertexAttribute<>() { + + private int[] sideRef; + + @Override + public int[] newArray(int length) { + return new int[length]; + } + + @Override + public boolean load(CCRenderState state) { + sideRef = state.model.getAttributes(this); + if (state.model.hasAttribute(this)) return sideRef != null; + + state.pipeline.addDependency(normalAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + if (sideRef != null) state.side = sideRef[state.vertexIndex]; + else state.side = CCModel.findSide(state.normal); + } + }; + /** + * Uses the position of the lightmatrix to compute LC if not provided + */ + public VertexAttribute lightCoordAttrib = new VertexAttribute<>() { + + private LC[] lcRef; + private final Vector3 vec = new Vector3(); // for computation + private final Vector3 pos = new Vector3(); + + @Override + public LC[] newArray(int length) { + return new LC[length]; + } + + @Override + public boolean load(CCRenderState state) { + lcRef = state.model.getAttributes(this); + if (state.model.hasAttribute(this)) return lcRef != null; + + pos.set(state.lightMatrix.pos.x, state.lightMatrix.pos.y, state.lightMatrix.pos.z); + state.pipeline.addDependency(sideAttrib); + state.pipeline.addRequirement(Transformation.operationIndex); + return true; + } + + @Override + public void operate(CCRenderState state) { + if (lcRef != null) state.lc.set(lcRef[state.vertexIndex]); + else state.lc.compute(vec.set(state.vert.vec).sub(pos), state.side); + } + }; + + // pipeline state + public IVertexSource model; + + public int firstVertexIndex; + public int lastVertexIndex; + public int vertexIndex; + + // context + public int baseColour; + public int alphaOverride; + public boolean useNormals; + public boolean computeLighting; + public boolean useColour; + public LightMatrix lightMatrix = new LightMatrix(); + + // vertex outputs + public Vertex5 vert = new Vertex5(); + public boolean hasNormal; + public Vector3 normal = new Vector3(); + + public boolean hasColour; + public int colour; + + public boolean hasBrightness; + public int brightness; + + // attribute storage + public int side; + public LC lc = new LC(); + + public static void reset() { + instance().resetInstance(); + } + + public void resetInstance() { + model = null; + pipeline.reset(); + useNormals = hasNormal = hasBrightness = hasColour = false; + useColour = computeLighting = true; + baseColour = alphaOverride = -1; + } + + public void setPipelineInstance(IVertexOperation... ops) { + pipeline.setPipeline(ops); + } + + @Deprecated + public static void setPipeline(IVertexOperation... ops) { + instance().setPipelineInstance(ops); + } + + public void setPipelineInstance(IVertexSource model, int start, int end, IVertexOperation... ops) { + pipeline.reset(); + setModelInstance(model, start, end); + pipeline.setPipeline(ops); + } + + @Deprecated + public static void setPipeline(IVertexSource model, int start, int end, IVertexOperation... ops) { + instance().setPipelineInstance(model, start, end, ops); + } + + public void bindModelInstance(IVertexSource model) { + if (this.model != model) { + this.model = model; + pipeline.rebuild(); + } + } + + @Deprecated + public static void bindModel(IVertexSource model) { + instance().bindModelInstance(model); + } + + public void setModelInstance(IVertexSource source) { + setModelInstance(source, 0, source.getVertices().length); + } + + @Deprecated + public static void setModel(IVertexSource source) { + instance().setModelInstance(source); + } + + public void setModelInstance(IVertexSource source, int start, int end) { + bindModelInstance(source); + setVertexRangeInstance(start, end); + } + + @Deprecated + public static void setModel(IVertexSource source, int start, int end) { + instance().setModelInstance(source, start, end); + } + + public void setVertexRangeInstance(int start, int end) { + firstVertexIndex = start; + lastVertexIndex = end; + } + + @Deprecated + public static void setVertexRange(int start, int end) { + instance().setVertexRangeInstance(start, end); + } + + public void renderInstance(IVertexOperation... ops) { + setPipelineInstance(ops); + renderInstance(); + } + + @Deprecated + public static void render(IVertexOperation... ops) { + instance().renderInstance(ops); + } + + public void renderInstance() { + Vertex5[] verts = model.getVertices(); + for (vertexIndex = firstVertexIndex; vertexIndex < lastVertexIndex; vertexIndex++) { + model.prepareVertex(this); + vert.set(verts[vertexIndex]); + runPipelineInstance(); + writeVertInstance(); + } + } + + @Deprecated + public static void render() { + instance().renderInstance(); + } + + public void runPipelineInstance() { + pipeline.operate(); + } + + @Deprecated + public static void runPipeline() { + instance().runPipelineInstance(); + } + + public void writeVertInstance() { + if (hasNormal) Tessellator.instance.setNormal((float) normal.x, (float) normal.y, (float) normal.z); + if (hasColour) Tessellator.instance.setColorRGBA( + colour >>> 24, + colour >> 16 & 0xFF, + colour >> 8 & 0xFF, + alphaOverride >= 0 ? alphaOverride : colour & 0xFF); + if (hasBrightness) Tessellator.instance.setBrightness(brightness); + Tessellator.instance.addVertexWithUV(vert.vec.x, vert.vec.y, vert.vec.z, vert.uv.u, vert.uv.v); + } + + @Deprecated + public static void writeVert() { + instance().writeVertInstance(); + } + + public void setNormalInstance(double x, double y, double z) { + hasNormal = true; + normal.set(x, y, z); + } + + @Deprecated + public static void setNormal(double x, double y, double z) { + instance().setNormalInstance(x, y, z); + } + + public void setNormalInstance(Vector3 n) { + hasNormal = true; + normal.set(n); + } + + @Deprecated + public static void setNormal(Vector3 n) { + instance().setNormalInstance(n); + } + + public void setColourInstance(int c) { + hasColour = true; + colour = c; + } + + @Deprecated + public static void setColour(int c) { + instance().setColourInstance(c); + } + + public void setBrightnessInstance(int b) { + hasBrightness = true; + brightness = b; + } + + @Deprecated + public static void setBrightness(int b) { + instance().setBrightnessInstance(b); + } + + public void setBrightnessInstance(IBlockAccess world, int x, int y, int z) { + setBrightnessInstance(world.getBlock(x, y, z).getMixedBrightnessForBlock(world, x, y, z)); + } + + @Deprecated + public static void setBrightness(IBlockAccess world, int x, int y, int z) { + instance().setBrightnessInstance(world, x, y, z); + } + + public static void pullLightmap() { + instance().pullLightmapInstance(); + } + + public void pullLightmapInstance() { + setBrightnessInstance((int) OpenGlHelper.lastBrightnessY << 16 | (int) OpenGlHelper.lastBrightnessX); + } + + public static void pushLightmap() { + instance().pushLightmapInstance(); + } + + public void pushLightmapInstance() { + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, brightness & 0xFFFF, brightness >>> 16); + } + + /** + * Compact helper for setting dynamic rendering context. Uses normals and doesn't compute lighting + */ + public static void setDynamic() { + instance().setDynamicInstance(); + } + + public void setDynamicInstance() { + useNormals = true; + computeLighting = false; + } + + public static void changeTexture(String texture) { + changeTexture(new ResourceLocation(texture)); + } + + public static void changeTexture(ResourceLocation texture) { + Minecraft.getMinecraft().renderEngine.bindTexture(texture); + } + + public void startDrawingInstance() { + startDrawingInstance(7); + } + + @Deprecated + public static void startDrawing() { + instance().startDrawingInstance(); + } + + public void startDrawingInstance(int mode) { + Tessellator.instance.startDrawing(mode); + if (hasColour) Tessellator.instance.setColorRGBA( + colour >>> 24, + colour >> 16 & 0xFF, + colour >> 8 & 0xFF, + alphaOverride >= 0 ? alphaOverride : colour & 0xFF); + if (hasBrightness) Tessellator.instance.setBrightness(brightness); + } + + @Deprecated + public static void startDrawing(int mode) { + instance().startDrawingInstance(mode); + } + + public static void draw() { + Tessellator.instance.draw(); + } + + public void drawInstance() { + Tessellator.instance.draw(); + } +} diff --git a/src/main/java/codechicken/lib/render/ColourMultiplier.java b/src/main/java/codechicken/lib/render/ColourMultiplier.java new file mode 100644 index 0000000..c764f89 --- /dev/null +++ b/src/main/java/codechicken/lib/render/ColourMultiplier.java @@ -0,0 +1,43 @@ +package codechicken.lib.render; + +import codechicken.lib.colour.ColourRGBA; + +public class ColourMultiplier implements CCRenderState.IVertexOperation { + + private static final ThreadLocal instances = ThreadLocal + .withInitial(() -> new ColourMultiplier(-1)); + + public static ColourMultiplier instance(int colour) { + ColourMultiplier instance = instances.get(); + instance.colour = colour; + return instance; + } + + public static final int operationIndex = CCRenderState.registerOperation(); + public int colour; + + public ColourMultiplier(int colour) { + this.colour = colour; + } + + @Override + public boolean load(CCRenderState state) { + if (colour == -1) { + state.setColourInstance(-1); + return false; + } + + state.pipeline.addDependency(state.colourAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + state.setColourInstance(ColourRGBA.multiply(state.colour, colour)); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/src/main/java/codechicken/lib/render/EntityDigIconFX.java b/src/main/java/codechicken/lib/render/EntityDigIconFX.java new file mode 100644 index 0000000..fd2e631 --- /dev/null +++ b/src/main/java/codechicken/lib/render/EntityDigIconFX.java @@ -0,0 +1,139 @@ +package codechicken.lib.render; + +import net.minecraft.client.particle.EffectRenderer; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.IIcon; +import net.minecraft.world.World; + +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.Vector3; + +public class EntityDigIconFX extends EntityFX { + + public EntityDigIconFX(World world, double x, double y, double z, double dx, double dy, double dz, IIcon icon) { + super(world, x, y, z, dx, dy, dz); + particleIcon = icon; + particleGravity = 1; + particleRed = particleGreen = particleBlue = 0.6F; + particleScale /= 2.0F; + } + + @Override + public int getFXLayer() { + return 1; + } + + public void setScale(float scale) { + particleScale = scale; + } + + public float getScale() { + return particleScale; + } + + public void setMaxAge(int age) { + particleMaxAge = age; + } + + public int getMaxAge() { + return particleMaxAge; + } + + /** + * copy pasted from EntityDiggingFX + */ + @Override + public void renderParticle(Tessellator par1Tessellator, float par2, float par3, float par4, float par5, float par6, + float par7) { + float f6 = (particleTextureIndexX + particleTextureJitterX / 4.0F) / 16.0F; + float f7 = f6 + 0.015609375F; + float f8 = (particleTextureIndexY + particleTextureJitterY / 4.0F) / 16.0F; + float f9 = f8 + 0.015609375F; + float f10 = 0.1F * particleScale; + + if (particleIcon != null) { + f6 = particleIcon.getInterpolatedU(particleTextureJitterX / 4.0F * 16.0F); + f7 = particleIcon.getInterpolatedU((particleTextureJitterX + 1.0F) / 4.0F * 16.0F); + f8 = particleIcon.getInterpolatedV(particleTextureJitterY / 4.0F * 16.0F); + f9 = particleIcon.getInterpolatedV((particleTextureJitterY + 1.0F) / 4.0F * 16.0F); + } + + float f11 = (float) (prevPosX + (posX - prevPosX) * par2 - interpPosX); + float f12 = (float) (prevPosY + (posY - prevPosY) * par2 - interpPosY); + float f13 = (float) (prevPosZ + (posZ - prevPosZ) * par2 - interpPosZ); + float f14 = 1.0F; + par1Tessellator.setColorOpaque_F(f14 * particleRed, f14 * particleGreen, f14 * particleBlue); + par1Tessellator.addVertexWithUV( + f11 - par3 * f10 - par6 * f10, + f12 - par4 * f10, + f13 - par5 * f10 - par7 * f10, + f6, + f9); + par1Tessellator.addVertexWithUV( + f11 - par3 * f10 + par6 * f10, + f12 + par4 * f10, + f13 - par5 * f10 + par7 * f10, + f6, + f8); + par1Tessellator.addVertexWithUV( + f11 + par3 * f10 + par6 * f10, + f12 + par4 * f10, + f13 + par5 * f10 + par7 * f10, + f7, + f8); + par1Tessellator.addVertexWithUV( + f11 + par3 * f10 - par6 * f10, + f12 - par4 * f10, + f13 + par5 * f10 - par7 * f10, + f7, + f9); + } + + public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon, + EffectRenderer effectRenderer) { + float border = 0.1F; + Vector3 diff = bounds.max.copy().subtract(bounds.min).add(-2 * border); + diff.x *= world.rand.nextDouble(); + diff.y *= world.rand.nextDouble(); + diff.z *= world.rand.nextDouble(); + Vector3 pos = diff.add(bounds.min).add(border); + + if (side == 0) diff.y = bounds.min.y - border; + if (side == 1) diff.y = bounds.max.y + border; + if (side == 2) diff.z = bounds.min.z - border; + if (side == 3) diff.z = bounds.max.z + border; + if (side == 4) diff.x = bounds.min.x - border; + if (side == 5) diff.x = bounds.max.x + border; + + effectRenderer.addEffect( + new EntityDigIconFX(world, pos.x, pos.y, pos.z, 0, 0, 0, icon).multiplyVelocity(0.2F) + .multipleParticleScaleBy(0.6F)); + } + + public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons, + EffectRenderer effectRenderer) { + Vector3 diff = bounds.max.copy().subtract(bounds.min); + Vector3 center = bounds.min.copy().add(bounds.max).multiply(0.5); + Vector3 density = diff.copy().multiply(4); + density.x = Math.ceil(density.x); + density.y = Math.ceil(density.y); + density.z = Math.ceil(density.z); + + for (int i = 0; i < density.x; ++i) for (int j = 0; j < density.y; ++j) for (int k = 0; k < density.z; ++k) { + double x = bounds.min.x + (i + 0.5) * diff.x / density.x; + double y = bounds.min.y + (j + 0.5) * diff.y / density.y; + double z = bounds.min.z + (k + 0.5) * diff.z / density.z; + effectRenderer.addEffect( + new EntityDigIconFX( + world, + x, + y, + z, + x - center.x, + y - center.y, + z - center.z, + icons[world.rand.nextInt(icons.length)])); + } + } +} diff --git a/src/main/java/codechicken/lib/render/FontUtils.java b/src/main/java/codechicken/lib/render/FontUtils.java new file mode 100644 index 0000000..8397d8a --- /dev/null +++ b/src/main/java/codechicken/lib/render/FontUtils.java @@ -0,0 +1,61 @@ +package codechicken.lib.render; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.item.ItemStack; + +import org.lwjgl.opengl.GL11; + +public class FontUtils { + + public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; + + public static void drawCenteredString(String s, int xCenter, int y, int colour) { + fontRenderer.drawString(s, xCenter - fontRenderer.getStringWidth(s) / 2, y, colour); + } + + public static void drawRightString(String s, int xRight, int y, int colour) { + fontRenderer.drawString(s, xRight - fontRenderer.getStringWidth(s), y, colour); + } + + public static final String[] prefixes = new String[] { "K", "M", "G" }; + + public static void drawItemQuantity(int x, int y, ItemStack item, String quantity, int mode) { + if (item == null || (quantity == null && item.stackSize <= 1)) return; + + if (quantity == null) { + switch (mode) { + case 2: + int q = item.stackSize; + String postfix = ""; + for (int p = 0; p < 3 && q > 1000; p++) { + q /= 1000; + postfix = prefixes[p]; + } + quantity = Integer.toString(q) + postfix; + case 1: + quantity = ""; + if (item.stackSize / 64 > 0) quantity += item.stackSize / 64 + "s"; + if (item.stackSize % 64 > 0) quantity += item.stackSize % 64; + break; + default: + quantity = Integer.toString(item.stackSize); + break; + } + } + + double scale = quantity.length() > 2 ? 0.5 : 1; + double sheight = 8 * scale; + double swidth = fontRenderer.getStringWidth(quantity) * scale; + + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glDisable(GL11.GL_DEPTH_TEST); + GL11.glPushMatrix(); + GL11.glTranslated(x + 16 - swidth, y + 16 - sheight, 0); + GL11.glScaled(scale, scale, 1); + fontRenderer.drawStringWithShadow(quantity, 0, 0, 0xFFFFFF); + GL11.glPopMatrix(); + GL11.glEnable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_DEPTH_TEST); + } +} diff --git a/src/main/java/codechicken/lib/render/IFaceRenderer.java b/src/main/java/codechicken/lib/render/IFaceRenderer.java new file mode 100644 index 0000000..2965346 --- /dev/null +++ b/src/main/java/codechicken/lib/render/IFaceRenderer.java @@ -0,0 +1,6 @@ +package codechicken.lib.render; + +public interface IFaceRenderer { + + public void renderFace(Vertex5[] face, int side); +} diff --git a/src/main/java/codechicken/lib/render/ManagedTextureFX.java b/src/main/java/codechicken/lib/render/ManagedTextureFX.java new file mode 100644 index 0000000..80377ca --- /dev/null +++ b/src/main/java/codechicken/lib/render/ManagedTextureFX.java @@ -0,0 +1,26 @@ +package codechicken.lib.render; + +public class ManagedTextureFX extends TextureFX { + + public boolean changed; + + public ManagedTextureFX(int size, String name) { + super(size, name); + imageData = new int[size * size]; + } + + @Override + public void setup() {} + + public void setData(int[] data) { + System.arraycopy(data, 0, imageData, 0, imageData.length); + changed = true; + } + + @Override + public boolean changed() { + boolean r = changed; + changed = false; + return r; + } +} diff --git a/src/main/java/codechicken/lib/render/PlaceholderTexture.java b/src/main/java/codechicken/lib/render/PlaceholderTexture.java new file mode 100644 index 0000000..3f9b9e2 --- /dev/null +++ b/src/main/java/codechicken/lib/render/PlaceholderTexture.java @@ -0,0 +1,22 @@ +package codechicken.lib.render; + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.util.ResourceLocation; + +public class PlaceholderTexture extends TextureAtlasSprite { + + protected PlaceholderTexture(String par1) { + super(par1); + } + + @Override + public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) { + return true; + } + + @Override + public boolean load(IResourceManager manager, ResourceLocation location) { + return true; + } +} diff --git a/src/main/java/codechicken/lib/render/QBImporter.java b/src/main/java/codechicken/lib/render/QBImporter.java new file mode 100644 index 0000000..5b5d457 --- /dev/null +++ b/src/main/java/codechicken/lib/render/QBImporter.java @@ -0,0 +1,710 @@ +package codechicken.lib.render; + +import java.awt.image.BufferedImage; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.util.IIcon; +import net.minecraft.util.ResourceLocation; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import codechicken.lib.render.uv.UV; +import codechicken.lib.render.uv.UVScale; +import codechicken.lib.vec.BlockCoord; +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.CuboidCoord; +import codechicken.lib.vec.Rectangle4i; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Scale; +import codechicken.lib.vec.Transformation; +import codechicken.lib.vec.Translation; +import codechicken.lib.vec.Vector3; + +public class QBImporter { + + private static class ImagePackNode { + + Rectangle4i rect; + ImagePackNode child1; + ImagePackNode child2; + QBImage packed; + + public ImagePackNode(int x, int y, int w, int h) { + rect = new Rectangle4i(x, y, w, h); + } + + public boolean pack(QBImage img) { + if (child1 != null) return child1.pack(img) || child2.pack(img); + + if (packed != null) return false; + + int fit = getFit(img.width(), img.height()); + if (fit == 0) return false; + + if ((fit & 2) != 0) { // exact fit + packed = img; + img.packSlot = rect; + img.packT = new ImageTransform((fit & 1) << 2); + return true; + } + + int w = (fit & 1) == 0 ? img.width() : img.height(); + int h = (fit & 1) == 0 ? img.height() : img.width(); + + if (rect.w - w > rect.h - h) { // create split with biggest leftover space + child1 = new ImagePackNode(rect.x, rect.y, w, rect.h); + child2 = new ImagePackNode(rect.x + w, rect.y, rect.w - w, rect.h); + } else { + child1 = new ImagePackNode(rect.x, rect.y, rect.w, h); + child2 = new ImagePackNode(rect.x, rect.y + h, rect.w, rect.h - h); + } + return child1.pack(img); + } + + private int getFit(int w, int h) { + if (w == rect.w && h == rect.h) return 2; + if (w == rect.h && h == rect.w) return 3; + if (rect.w >= w && rect.h >= h) return 4; + if (rect.w >= h && rect.h >= w) return 5; + + return 0; + } + + private static void nextSize(Rectangle4i rect, boolean square) { + if (square) { + rect.w <<= 1; + rect.h <<= 1; + } else { + if (rect.w == rect.h) rect.w *= 2; + else rect.h *= 2; + } + } + + public static ImagePackNode pack(List images, boolean square) { + Collections.sort(images); + + int area = 0; + for (QBImage img : images) area += img.area(); + + ImagePackNode node = new ImagePackNode(0, 0, 2, 2); + while (node.rect.area() < area) nextSize(node.rect, square); + + while (true) { + boolean packed = true; + for (QBImage img : images) if (!node.pack(img)) { + packed = false; + break; + } + + if (packed) return node; + + node.child1 = node.child2 = null; + nextSize(node.rect, square); + } + } + + public BufferedImage toImage() { + BufferedImage img = new BufferedImage(rect.w, rect.h, BufferedImage.TYPE_INT_ARGB); + write(img); + return img; + } + + private void write(BufferedImage img) { + if (child1 != null) { + child1.write(img); + child2.write(img); + } else if (packed != null) { + ImageTransform t = packed.packT; + for (int u = 0; u < rect.w; u++) for (int v = 0; v < rect.h; v++) { + int rgba = t.access(packed, u, v); + img.setRGB(u + rect.x, v + rect.y, rgba >>> 8 | rgba << 24); + } + } + } + } + + private static class ImageTransform { + + int transform; + + public ImageTransform(int i) { + transform = i; + } + + public ImageTransform() { + this(0); + } + + public boolean transpose() { + return (transform & 4) != 0; + } + + public boolean flipU() { + return (transform & 1) != 0; + } + + public boolean flipV() { + return (transform & 2) != 0; + } + + public int access(QBImage img, int u, int v) { + if (transpose()) { + int tmp = u; + u = v; + v = tmp; + } + if (flipU()) u = img.width() - 1 - u; + if (flipV()) v = img.height() - 1 - v; + + return img.data[u][v]; + } + + public UV transform(UV uv) { + if (transpose()) { + double tmp = uv.u; + uv.u = uv.v; + uv.v = tmp; + } + if (flipU()) uv.u = 1 - uv.u; + if (flipV()) uv.v = 1 - uv.v; + + return uv; + } + } + + public static class QBImage implements Comparable { + + int[][] data; + ImageTransform packT; + Rectangle4i packSlot; + + public int width() { + return data.length; + } + + public int height() { + return data[0].length; + } + + public int area() { + return width() * height(); + } + + @Override + public int compareTo(QBImage o) { + int a = area(); + int b = o.area(); + return a > b ? -1 : a == b ? 0 : 1; + } + + public ImageTransform transformTo(QBImage img) { + if (width() == img.width() && height() == img.height()) for (int i = 0; i < 4; i++) { + ImageTransform t = new ImageTransform(i); + if (equals(img, t)) return t; + } + if (width() == img.height() && height() == img.width()) for (int i = 4; i < 8; i++) { + ImageTransform t = new ImageTransform(i); + if (equals(img, t)) return t; + } + return null; + } + + public boolean equals(QBImage img, ImageTransform t) { + for (int u = 0; u < img.width(); u++) + for (int v = 0; v < img.height(); v++) if (t.access(this, u, v) != img.data[u][v]) return false; + + return true; + } + + public void transform(UV uv) { + packT.transform(uv); + uv.u *= packSlot.w; + uv.v *= packSlot.h; + uv.u += packSlot.x; + uv.v += packSlot.y; + } + } + + private static final int[][] vertOrder = new int[][] { // clockwise because MC is left handed + { 3, 0 }, { 1, 0 }, { 1, 2 }, { 3, 2 } }; + + public static class QBQuad { + + public Vertex5[] verts = new Vertex5[4]; + public QBImage image = new QBImage(); + public ImageTransform t = new ImageTransform(); + public int side; + + public QBQuad(int side) { + this.side = side; + } + + public void applyImageT() { + for (Vertex5 vert : verts) { + t.transform(vert.uv); + image.transform(vert.uv); + } + } + + public static QBQuad restore(Rectangle4i flat, int side, double d, QBImage img) { + QBQuad quad = new QBQuad(side); + quad.image = img; + + Transformation t = new Scale(-1, 1, -1).with(Rotation.sideOrientation(side, 0)) + .with(new Translation(new Vector3().setSide(side, d))); + quad.verts[0] = new Vertex5(flat.x, 0, flat.y, 0, 0); + quad.verts[1] = new Vertex5(flat.x + flat.w, 0, flat.y, 1, 0); + quad.verts[2] = new Vertex5(flat.x + flat.w, 0, flat.y + flat.h, 1, 1); + quad.verts[3] = new Vertex5(flat.x, 0, flat.y + flat.h, 0, 1); + for (Vertex5 vert : quad.verts) vert.apply(t); + + return quad; + } + + public Rectangle4i flatten() { + Transformation t = Rotation.sideOrientation(side, 0).inverse().with(new Scale(-1, 0, -1)); + Vector3 vmin = verts[0].vec.copy().apply(t); + Vector3 vmax = verts[2].vec.copy().apply(t); + return new Rectangle4i((int) vmin.x, (int) vmin.z, (int) (vmax.x - vmin.x), (int) (vmax.z - vmin.z)); + } + } + + public static class QBCuboid { + + public QBMatrix mat; + public CuboidCoord c; + public int sides; + + public QBCuboid(QBMatrix mat, CuboidCoord c) { + this.mat = mat; + this.c = c; + sides = 0; + } + + public static boolean intersects(QBCuboid a, QBCuboid b) { + CuboidCoord c = a.c; + CuboidCoord d = b.c; + return c.min.x <= d.max.x && d.min.x <= c.max.x + && c.min.y <= d.max.y + && d.min.y <= c.max.y + && c.min.z <= d.max.z + && d.min.z <= c.max.z; + } + + public static void clip(QBCuboid a, QBCuboid b) { + if (intersects(a, b)) { + a.clip(b); + b.clip(a); + } + } + + public void clip(QBCuboid o) { + CuboidCoord d = o.c; + for (int a = 0; a < 6; a += 2) { + int a1 = (a + 2) % 6; + int a2 = (a + 4) % 6; + if (c.getSide(a1 + 1) <= d.getSide(a1 + 1) && c.getSide(a1) >= d.getSide(a1) + && c.getSide(a2 + 1) <= d.getSide(a2 + 1) + && c.getSide(a2) >= d.getSide(a2)) { + + if (c.getSide(a) <= d.getSide(a + 1) && c.getSide(a) >= d.getSide(a)) { + c.setSide(a, d.getSide(a + 1) + 1); + sides |= 1 << a; + } + if (c.getSide(a + 1) >= d.getSide(a) && c.getSide(a + 1) <= d.getSide(a + 1)) { + c.setSide(a + 1, d.getSide(a) - 1); + sides |= 2 << a; + } + } + } + } + + public void extractQuads(List quads) { + Cuboid6 box = c.bounds(); + for (int s = 0; s < 6; s++) if ((sides & 1 << s) == 0) quads.add(extractQuad(s, box)); + } + + private QBQuad extractQuad(int side, Cuboid6 box) { + double[] da = new double[3]; + da[side >> 1] = box.getSide(side); + + QBQuad quad = new QBQuad(side); + for (int i = 0; i < 4; i++) { + int rU = vertOrder[i][0]; + int rV = vertOrder[i][1]; + int sideU = Rotation.rotateSide(side, rU); + int sideV = Rotation.rotateSide(side, rV); + da[sideU >> 1] = box.getSide(sideU); + da[sideV >> 1] = box.getSide(sideV); + quad.verts[i] = new Vertex5(Vector3.fromAxes(da), (3 - rU) / 2, rV / 2); + } + + int sideU = Rotation.rotateSide(side, 1); + int sideV = Rotation.rotateSide(side, 2); + quad.image.data = new int[c.size(sideU)][c.size(sideV)]; + QBImage image = quad.image; + + int[] ia = new int[3]; + ia[side >> 1] = c.getSide(side); + ia[sideU >> 1] = c.getSide(sideU ^ 1); + ia[sideV >> 1] = c.getSide(sideV ^ 1); + BlockCoord b = BlockCoord.fromAxes(ia); + BlockCoord bU = BlockCoord.sideOffsets[sideU]; + BlockCoord bV = BlockCoord.sideOffsets[sideV]; + for (int u = 0; u < image.width(); u++) for (int v = 0; v < image.height(); v++) + image.data[u][v] = mat.matrix[b.x + bU.x * u + bV.x * v][b.y + bU.y * u + bV.y * v][b.z + bU.z * u + + bV.z * v]; + + return quad; + } + } + + public static class QBMatrix { + + public String name; + public BlockCoord pos; + public BlockCoord size; + public int[][][] matrix; + + public void readMatrix(DataInputStream din, boolean compressed) throws IOException { + if (compressed) { + int z = 0; + while (z < size.z) { + int index = 0; + + while (true) { + int data = din.readInt(); + + if (data == NEXTSLICEFLAG) break; + + if (data == CODEFLAG) { + int count = readTni(din); + data = din.readInt(); + + for (int j = 0; j < count; j++, index++) matrix[index % size.x][index / size.x][z] = data; + } else { + matrix[index % size.x][index / size.x][z] = data; + index++; + } + } + z++; + } + } else { + for (int z = 0; z < size.z; z++) + for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) matrix[x][y][z] = din.readInt(); + } + } + + public void convertBGRAtoRGBA() { + for (int z = 0; z < size.z; z++) for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) { + int i = matrix[x][y][z]; + matrix[x][y][z] = Integer.reverseBytes(i >>> 8) | i & 0xFF; + } + } + + private boolean voxelFull(boolean[][][] solid, CuboidCoord c) { + for (BlockCoord b : c) if (matrix[b.x][b.y][b.z] == 0) return false; + for (BlockCoord b : c) solid[b.x][b.y][b.z] = false; + return true; + } + + private QBCuboid expand(boolean[][][] solid, BlockCoord b) { + CuboidCoord c = new CuboidCoord(b); + solid[b.x][b.y][b.z] = false; + + for (int s = 0; s < 6; s++) { + CuboidCoord slice = c.copy(); + slice.expand(s ^ 1, -(slice.size(s) - 1)); + slice.expand(s, 1); + + while (slice.getSide(s) >= 0 && slice.getSide(s) < size.getSide(s)) { + if (!voxelFull(solid, slice)) break; + slice.expand(s ^ 1, -1); + slice.expand(s, 1); + c.expand(s, 1); + } + } + return new QBCuboid(this, c); + } + + public List rectangulate() { + List list = new ArrayList(); + boolean[][][] solid = new boolean[size.x][size.y][size.z]; + for (int z = 0; z < size.z; z++) + for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) solid[x][y][z] = matrix[x][y][z] != 0; + + for (int x = 0; x < size.x; x++) for (int z = 0; z < size.z; z++) + for (int y = 0; y < size.y; y++) if (solid[x][y][z]) list.add(expand(solid, new BlockCoord(x, y, z))); + + for (int i = 0; i < list.size(); i++) + for (int j = i + 1; j < list.size(); j++) QBCuboid.clip(list.get(i), list.get(j)); + + return list; + } + + public List extractQuads(boolean texturePlanes) { + List quads = new LinkedList(); + for (QBCuboid c : rectangulate()) c.extractQuads(quads); + + if (texturePlanes) optimisePlanes(quads); + + return quads; + } + + private void optimisePlanes(List quads) { + Multimap map = HashMultimap.create(); + for (QBQuad quad : quads) map.put(quad.side | ((int) quad.verts[0].vec.getSide(quad.side)) << 3, quad); + + quads.clear(); + for (Integer key : map.keySet()) { + Collection plane = map.get(key); + if (plane.size() == 1) { + quads.add(plane.iterator().next()); + continue; + } + + int side = key & 7; + Rectangle4i rect = null; + for (QBQuad q : plane) if (rect == null) rect = q.flatten(); + else rect.include(q.flatten()); + + QBImage img = new QBImage(); + img.data = new int[rect.w][rect.h]; + for (QBQuad q : plane) { + QBImage from = q.image; + Rectangle4i r = q.flatten(); + int du = r.x - rect.x; + int dv = r.y - rect.y; + for (int u = 0; u < from.width(); u++) + for (int v = 0; v < from.height(); v++) img.data[du + u][dv + v] = from.data[u][v]; + } + + quads.add(QBQuad.restore(rect, side, key >> 3, img)); + } + } + + public CCModel buildModel(List quads, BufferedImage img, boolean scaleMC) { + CCModel m = CCModel.quadModel(quads.size() * 4); + int i = 0; + for (QBQuad quad : quads) { + quad.applyImageT(); + m.verts[i++] = quad.verts[0]; + m.verts[i++] = quad.verts[1]; + m.verts[i++] = quad.verts[2]; + m.verts[i++] = quad.verts[3]; + } + m.apply(new UVScale(1D / img.getWidth(), 1D / img.getHeight())); + m.apply(new Translation(pos.x, pos.y, pos.z)); + if (scaleMC) m.apply(new Scale(1 / 16D)); + m.computeNormals(); + return m; + } + + private static void addImages(List quads, List images) { + for (QBQuad q : quads) { + QBImage img = q.image; + boolean matched = false; + for (QBImage img2 : images) { + ImageTransform t = img.transformTo(img2); + if (t != null) { + q.t = t; + q.image = img2; + matched = true; + break; + } + } + if (!matched) images.add(img); + } + } + } + + public static final int TEXTUREPLANES = 1; + public static final int SQUARETEXTURE = 2; + public static final int MERGETEXTURES = 4; + public static final int SCALEMC = 8; + + public static class QBModel { + + public QBMatrix[] matrices; + public boolean rightHanded; + + public RasterisedModel toRasterisedModel(int flags) { + List qbImages = new ArrayList(); + List> modelQuads = new ArrayList>(); + List images = new ArrayList(); + + boolean texturePlanes = (flags & TEXTUREPLANES) != 0; + boolean squareTextures = (flags & SQUARETEXTURE) != 0; + boolean mergeTextures = (flags & MERGETEXTURES) != 0; + boolean scaleMC = (flags & SCALEMC) != 0; + + for (QBMatrix mat : matrices) { + List quads = mat.extractQuads(texturePlanes); + modelQuads.add(quads); + QBMatrix.addImages(quads, qbImages); + if (!mergeTextures) { + images.add(ImagePackNode.pack(qbImages, squareTextures).toImage()); + qbImages.clear(); + } + } + + if (mergeTextures) images.add(ImagePackNode.pack(qbImages, squareTextures).toImage()); + + RasterisedModel m = new RasterisedModel(images); + for (int i = 0; i < matrices.length; i++) { + QBMatrix mat = matrices[i]; + BufferedImage img = images.get(mergeTextures ? 0 : i); + m.add(mat.name, mat.buildModel(modelQuads.get(i), img, scaleMC)); + } + return m; + } + } + + public static class RasterisedModel { + + private class Holder { + + CCModel m; + int img; + + public Holder(CCModel m, int img) { + this.m = m; + this.img = img; + } + } + + private Map map = new HashMap(); + private List images; + private String[] icons; + + public RasterisedModel(List images) { + this.images = images; + icons = new String[images.size()]; + } + + public void add(String name, CCModel m) { + map.put(name, new Holder(m, Math.min(map.size(), images.size() - 1))); + } + + public CCModel getModel(String key) { + return map.get(key).m; + } + + public IIcon getIcon(String key, IIconRegister r, String iconName) { + int img = map.get(key).img; + if (icons[img] != null && !iconName.equals(icons[img])) throw new IllegalArgumentException( + "Attempted to get a previously registered icon by a different name: " + icons[img] + + ", " + + iconName); + if (icons[img] != null) return r.registerIcon(iconName); + + icons[img] = iconName; + return TextureUtils.getTextureSpecial(r, iconName).addTexture(new TextureDataHolder(images.get(img))); + } + + private void exportImg(BufferedImage img, File imgFile) throws IOException { + if (!imgFile.exists()) imgFile.createNewFile(); + ImageIO.write(img, "PNG", imgFile); + } + + public void export(File objFile, File imgDir) { + try { + if (!objFile.exists()) objFile.createNewFile(); + if (!imgDir.exists()) imgDir.mkdirs(); + + Map modelMap = new HashMap(); + for (Map.Entry e : map.entrySet()) modelMap.put(e.getKey(), e.getValue().m); + + PrintWriter p = new PrintWriter(objFile); + CCModel.exportObj(modelMap, p); + p.close(); + + if (images.size() < map.size()) + exportImg(images.get(0), new File(imgDir, objFile.getName().replaceAll("(.+)\\..+", "$1.png"))); + else for (Map.Entry e : map.entrySet()) + exportImg(images.get(e.getValue().img), new File(imgDir, e.getKey() + ".png")); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private static String readAsciiString(DataInputStream din) throws IOException { + byte[] bytes = new byte[din.readByte() & 0xFF]; + din.readFully(bytes); + return new String(bytes, "US-ASCII"); + } + + private static int readTni(DataInputStream din) throws IOException { + return Integer.reverseBytes(din.readInt()); + } + + private static final int CODEFLAG = Integer.reverseBytes(2); + private static final int NEXTSLICEFLAG = Integer.reverseBytes(6); + + public static QBModel loadQB(InputStream input) throws IOException { + DataInputStream din = new DataInputStream(input); + + QBModel m = new QBModel(); + int version = din.readInt(); + int colorFormat = din.readInt(); + m.rightHanded = din.readInt() != 0; + boolean compressed = din.readInt() != 0; + boolean visEncoded = din.readInt() != 0; + + if (visEncoded) throw new IllegalArgumentException("Encoded Visiblity States not supported"); + + m.matrices = new QBMatrix[readTni(din)]; + for (int i = 0; i < m.matrices.length; i++) { + QBMatrix mat = new QBMatrix(); + m.matrices[i] = mat; + mat.name = readAsciiString(din); + mat.size = new BlockCoord(readTni(din), readTni(din), readTni(din)); + mat.pos = new BlockCoord(readTni(din), readTni(din), readTni(din)); + mat.matrix = new int[mat.size.x][mat.size.y][mat.size.z]; + mat.readMatrix(din, compressed); + if (colorFormat == 1) mat.convertBGRAtoRGBA(); + } + + return m; + } + + public static QBModel loadQB(ResourceLocation res) { + try { + return loadQB(Minecraft.getMinecraft().getResourceManager().getResource(res).getInputStream()); + } catch (Exception e) { + throw new RuntimeException("failed to load model: " + res, e); + } + } + + public static QBModel loadQB(File file) { + try { + FileInputStream fin = new FileInputStream(file); + try { + return loadQB(fin); + } finally { + fin.close(); + } + } catch (Exception e) { + throw new RuntimeException("failed to load model: " + file.getPath(), e); + } + } +} diff --git a/src/main/java/codechicken/lib/render/RenderUtils.java b/src/main/java/codechicken/lib/render/RenderUtils.java new file mode 100644 index 0000000..57cfb32 --- /dev/null +++ b/src/main/java/codechicken/lib/render/RenderUtils.java @@ -0,0 +1,429 @@ +package codechicken.lib.render; + +import static net.minecraftforge.client.IItemRenderer.ItemRenderType.ENTITY; +import static net.minecraftforge.client.IItemRenderer.ItemRendererHelper.BLOCK_3D; + +import net.minecraft.block.Block; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.entity.RenderItem; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IIcon; +import net.minecraft.util.MathHelper; +import net.minecraftforge.client.IItemRenderer; +import net.minecraftforge.client.MinecraftForgeClient; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; + +import org.lwjgl.opengl.GL11; + +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.Rectangle4i; +import codechicken.lib.vec.Vector3; + +public class RenderUtils { + + static RenderItem uniformRenderItem; + static EntityItem entityItem; + + static { + uniformRenderItem = new RenderItem() { + + public boolean shouldBob() { + return false; + } + }; + uniformRenderItem.setRenderManager(RenderManager.instance); + entityItem = new EntityItem(null); + entityItem.hoverStart = 0; + } + + @Deprecated + public static void renderFluidQuad(Vector3 point1, Vector3 point2, Vector3 point3, Vector3 point4, IIcon icon, + double res) { + // spotless:off + renderFluidQuad( + point2.x, point2.y, point2.z, + point4.x - point1.x, point4.y - point1.y, point4.z - point1.z, + point1.x - point2.x, point1.y - point2.y, point1.z - point2.z, + icon, res); + } + + /** + * Draws a tessellated quadrilateral bottom to top, left to right + * + * @param base The bottom left corner of the quad + * @param wide The bottom of the quad + * @param high The left side of the quad + * @param res Units per icon + */ + @Deprecated + public static void renderFluidQuad(Vector3 base, Vector3 wide, Vector3 high, IIcon icon, double res) { + renderFluidQuad( + base.x, base.y, base.z, + wide.x, wide.y, wide.z, + wide.x, wide.y, wide.z, + icon, res); + } + + /** + * Draws a tessellated quadrilateral bottom to top, left to right + *

+ * base : The bottom left corner of the quad + *

+ * wide : The bottom of the quad + *

+ * high : The left side of the quad + *

+ * res : Units per icon + */ + public static void renderFluidQuad( + double baseX, double baseY, double baseZ, + double wideX, double wideY, double wideZ, + double highX, double highY, double highZ, + IIcon icon, double res) { + + Tessellator tessellator = Tessellator.instance; + + double u = icon.getMinU(); + double du = icon.getMaxU() - icon.getMinU(); + double v = icon.getMinV(); + double dv = icon.getMaxV() - icon.getMinV(); + + double wideLen = Math.sqrt(wideX * wideX + wideY * wideY + wideZ * wideZ); + double highLen = Math.sqrt(highX * highX + highY * highY + highZ * highZ); + + double x = 0; + while (x < wideLen) { + double rx = wideLen - x; + if (rx > res) rx = res; + + double y = 0; + while (y < highLen) { + double ry = highLen - y; + if (ry > res) ry = res; + + final double mult1 = x / wideLen; + double dx1X = wideX * mult1; + double dx1Y = wideY * mult1; + double dx1Z = wideZ * mult1; + + final double mult2 = (x + rx) / wideLen; + double dx2X = wideX * mult2; + double dx2Y = wideY * mult2; + double dx2Z = wideZ * mult2; + + final double mult3 = y / highLen; + double dy1X = highX * mult3; + double dy1Y = highY * mult3; + double dy1Z = highZ * mult3; + + final double mult4 = (y + ry) / highLen; + double dy2X = highX * mult4; + double dy2Y = highY * mult4; + double dy2Z = highZ * mult4; + + tessellator.addVertexWithUV( + baseX + dx1X + dy2X, + baseY + dx1Y + dy2Y, + baseZ + dx1Z + dy2Z, + u, + v + ry / res * dv); + tessellator.addVertexWithUV( + baseX + dx1X + dy1X, + baseY + dx1Y + dy1Y, + baseZ + dx1Z + dy1Z, + u, + v); + tessellator.addVertexWithUV( + baseX + dx2X + dy1X, + baseY + dx2Y + dy1Y, + baseZ + dx2Z + dy1Z, + u + rx / res * du, + v); + tessellator.addVertexWithUV( + baseX + dx2X + dy2X, + baseY + dx2Y + dy2Y, + baseZ + dx2Z + dy2Z, + u + rx / res * du, + v + ry / res * dv); + + y += ry; + } + + x += rx; + } + } + + public static void translateToWorldCoords(Entity entity, float partialTicks) { + double interpPosX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks; + double interpPosY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks; + double interpPosZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks; + GL11.glTranslated(-interpPosX, -interpPosY, -interpPosZ); + } + + public static void drawCuboidOutline(Cuboid6 c) { + Tessellator tess = Tessellator.instance; + tess.startDrawing(3); + tess.addVertex(c.min.x, c.min.y, c.min.z); + tess.addVertex(c.max.x, c.min.y, c.min.z); + tess.addVertex(c.max.x, c.min.y, c.max.z); + tess.addVertex(c.min.x, c.min.y, c.max.z); + tess.addVertex(c.min.x, c.min.y, c.min.z); + tess.draw(); + tess.startDrawing(3); + tess.addVertex(c.min.x, c.max.y, c.min.z); + tess.addVertex(c.max.x, c.max.y, c.min.z); + tess.addVertex(c.max.x, c.max.y, c.max.z); + tess.addVertex(c.min.x, c.max.y, c.max.z); + tess.addVertex(c.min.x, c.max.y, c.min.z); + tess.draw(); + tess.startDrawing(1); + tess.addVertex(c.min.x, c.min.y, c.min.z); + tess.addVertex(c.min.x, c.max.y, c.min.z); + tess.addVertex(c.max.x, c.min.y, c.min.z); + tess.addVertex(c.max.x, c.max.y, c.min.z); + tess.addVertex(c.max.x, c.min.y, c.max.z); + tess.addVertex(c.max.x, c.max.y, c.max.z); + tess.addVertex(c.min.x, c.min.y, c.max.z); + tess.addVertex(c.min.x, c.max.y, c.max.z); + tess.draw(); + } + + public static void renderFluidCuboid(CCRenderState state, Cuboid6 bound, IIcon tex, double res) { + renderFluidCuboid(bound, tex, res); + } + + public static void renderFluidCuboid(Cuboid6 bound, IIcon tex, double res) { + final double minX = bound.min.x; + final double minY = bound.min.y; + final double minZ = bound.min.z; + final double maxX = bound.max.x; + final double maxY = bound.max.y; + final double maxZ = bound.max.z; + renderFluidQuad( // bottom + minX, minY, minZ, + maxX - minX, 0, 0, + 0, 0, maxZ - minZ, + tex, res); + renderFluidQuad( // top + minX, maxY, minZ, + 0, 0, maxZ - minZ, + maxX - minX, 0, 0, + tex, res); + renderFluidQuad( // -x + minX, maxY, minZ, + 0, minY - maxY, 0, + 0, 0, maxZ - minZ, + tex, res); + renderFluidQuad( // +x + maxX, maxY, maxZ, + 0, minY - maxY, 0, + 0, 0, minZ - maxZ, + tex, res); + renderFluidQuad( // -z + maxX, maxY, minZ, + 0, minY - maxY, 0, + minX - maxX, 0, 0, + tex, res); + renderFluidQuad( // +z + minX, maxY, maxZ, + 0, minY - maxY, 0, + maxX - minX, 0, 0, + tex, res); + } + // spotless:on + + public static void renderBlockOverlaySide(int x, int y, int z, int side, double tx1, double tx2, double ty1, + double ty2) { + Tessellator tessellator = Tessellator.instance; + final double minX = x - 0.009; + final double maxX = x + 1.009; + final double minY = y - 0.009; + final double maxY = y + 1.009; + final double minZ = z - 0.009; + final double maxZ = z + 1.009; + switch (side) { + case 0: + tessellator.addVertexWithUV(minX, minY, minZ, tx1, ty1); + tessellator.addVertexWithUV(maxX, minY, minZ, tx2, ty1); + tessellator.addVertexWithUV(maxX, minY, maxZ, tx2, ty2); + tessellator.addVertexWithUV(minX, minY, maxZ, tx1, ty2); + break; + case 1: + tessellator.addVertexWithUV(maxX, maxY, minZ, tx2, ty1); + tessellator.addVertexWithUV(minX, maxY, minZ, tx1, ty1); + tessellator.addVertexWithUV(minX, maxY, maxZ, tx1, ty2); + tessellator.addVertexWithUV(maxX, maxY, maxZ, tx2, ty2); + break; + case 2: + tessellator.addVertexWithUV(minX, maxY, minZ, tx2, ty1); + tessellator.addVertexWithUV(maxX, maxY, minZ, tx1, ty1); + tessellator.addVertexWithUV(maxX, minY, minZ, tx1, ty2); + tessellator.addVertexWithUV(minX, minY, minZ, tx2, ty2); + break; + case 3: + tessellator.addVertexWithUV(maxX, maxY, maxZ, tx2, ty1); + tessellator.addVertexWithUV(minX, maxY, maxZ, tx1, ty1); + tessellator.addVertexWithUV(minX, minY, maxZ, tx1, ty2); + tessellator.addVertexWithUV(maxX, minY, maxZ, tx2, ty2); + break; + case 4: + tessellator.addVertexWithUV(minX, maxY, maxZ, tx2, ty1); + tessellator.addVertexWithUV(minX, maxY, minZ, tx1, ty1); + tessellator.addVertexWithUV(minX, minY, minZ, tx1, ty2); + tessellator.addVertexWithUV(minX, minY, maxZ, tx2, ty2); + break; + case 5: + tessellator.addVertexWithUV(maxX, maxY, minZ, tx2, ty1); + tessellator.addVertexWithUV(maxX, maxY, maxZ, tx1, ty1); + tessellator.addVertexWithUV(maxX, minY, maxZ, tx1, ty2); + tessellator.addVertexWithUV(maxX, minY, minZ, tx2, ty2); + break; + } + } + + public static boolean shouldRenderFluid(FluidStack stack) { + return stack.amount > 0 && stack.getFluid() != null; + } + + /** + * @param stack The fluid stack to render + * @return The icon of the fluid + */ + public static IIcon prepareFluidRender(CCRenderState state, FluidStack stack, int alpha) { + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + Fluid fluid = stack.getFluid(); + state.setColourInstance(fluid.getColor(stack) << 8 | alpha); + TextureUtils.bindAtlas(fluid.getSpriteNumber()); + return TextureUtils.safeIcon(fluid.getIcon(stack)); + } + + public static IIcon prepareFluidRender(FluidStack stack, int alpha) { + return prepareFluidRender(CCRenderState.instance(), stack, alpha); + } + + /** + * Re-enables lighting and disables blending. + */ + public static void postFluidRender() { + GL11.glEnable(GL11.GL_LIGHTING); + GL11.glDisable(GL11.GL_BLEND); + } + + public static double fluidDensityToAlpha(double d) { + return Math.pow(d, 0.4); + } + + /** + * Renders a fluid within a bounding box. If the fluid is a liquid it will render as a normal tank with height equal + * to fillRate/bound.height. If the fluid is a gas, it will render the full box with an alpha equal to fillRate. + * Warning, bound will be mutated if the fluid is a liquid + * + * @param stack The fluid to render. + * @param bound The bounding box within which the fluid is contained. + * @param fillRate The volume of fluid / the capacity of the tank. This is a double between 0 and 1. + * @param res The resolution to render at. + */ + public static void renderFluidCuboid(CCRenderState state, FluidStack stack, Cuboid6 bound, double fillRate, + double res) { + if (!shouldRenderFluid(stack)) return; + + fillRate = MathHelper.clamp_double(fillRate, 0d, 1d); + int alpha = 255; + if (stack.getFluid().isGaseous()) { + alpha = (int) (fluidDensityToAlpha(fillRate) * 255); + } else { + bound.max.y = bound.min.y + (bound.max.y - bound.min.y) * fillRate; + } + + IIcon tex = prepareFluidRender(state, stack, alpha); + state.startDrawingInstance(); + renderFluidCuboid(bound, tex, res); + state.drawInstance(); + postFluidRender(); + } + + /** + * Renders a fluid within a bounding box. If the fluid is a liquid it will render as a normal tank with height equal + * to fillRate/bound.height. If the fluid is a gas, it will render the full box with an alpha equal to fillRate. + * Warning, bound will be mutated if the fluid is a liquid + * + * @param stack The fluid to render. + * @param bound The bounding box within which the fluid is contained. + * @param fillRate The volume of fluid / the capacity of the tank. This is a double between 0 and 1. + * @param res The resolution to render at. + */ + public static void renderFluidCuboid(FluidStack stack, Cuboid6 bound, double fillRate, double res) { + renderFluidCuboid(CCRenderState.instance(), stack, bound, fillRate, res); + } + + public static void renderFluidGauge(CCRenderState state, FluidStack stack, Rectangle4i rect, double fillRate, + double res) { + if (!shouldRenderFluid(stack)) return; + + fillRate = MathHelper.clamp_double(fillRate, 0d, 1d); + int alpha = 255; + if (stack.getFluid().isGaseous()) { + alpha = (int) (fluidDensityToAlpha(fillRate) * 255); + } else { + int height = (int) (rect.h * fillRate); + rect.y += rect.h - height; + rect.h = height; + } + + IIcon tex = prepareFluidRender(state, stack, alpha); + state.startDrawingInstance(); + renderFluidQuad(rect.x, rect.y + rect.h, 0, rect.w, 0, 0, 0, -rect.h, 0, tex, res); + state.drawInstance(); + postFluidRender(); + } + + public static void renderFluidGauge(FluidStack stack, Rectangle4i rect, double fillRate, double res) { + renderFluidGauge(CCRenderState.instance(), stack, rect, fillRate, res); + } + + /** + * Renders items and blocks in the world at 0,0,0 with transformations that size them appropriately + */ + public static void renderItemUniform(ItemStack item) { + renderItemUniform(item, 0); + } + + /** + * Renders items and blocks in the world at 0,0,0 with transformations that size them appropriately + * + * @param spin The spin angle of the item around the y axis in degrees + */ + public static void renderItemUniform(ItemStack item, double spin) { + IItemRenderer customRenderer = MinecraftForgeClient.getItemRenderer(item, ENTITY); + boolean is3D = customRenderer != null && customRenderer.shouldUseRenderHelper(ENTITY, item, BLOCK_3D); + + boolean larger = false; + if (item.getItem() instanceof ItemBlock + && RenderBlocks.renderItemIn3d(Block.getBlockFromItem(item.getItem()).getRenderType())) { + int renderType = Block.getBlockFromItem(item.getItem()).getRenderType(); + larger = !(renderType == 1 || renderType == 19 || renderType == 12 || renderType == 2); + } else if (is3D) { + larger = true; + } + + double d = 2; + double d1 = 1 / d; + if (larger) GL11.glScaled(d, d, d); + + GL11.glColor4f(1, 1, 1, 1); + + entityItem.setEntityItemStack(item); + uniformRenderItem.doRender(entityItem, 0, larger ? 0.09 : 0.06, 0, 0, (float) (spin * 9 / Math.PI)); + + if (larger) GL11.glScaled(d1, d1, d1); + } +} diff --git a/src/main/java/codechicken/lib/render/ShaderProgram.java b/src/main/java/codechicken/lib/render/ShaderProgram.java new file mode 100644 index 0000000..60a3d69 --- /dev/null +++ b/src/main/java/codechicken/lib/render/ShaderProgram.java @@ -0,0 +1,125 @@ +package codechicken.lib.render; + +import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB; +import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_INFO_LOG_LENGTH_ARB; +import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_LINK_STATUS_ARB; +import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_VALIDATE_STATUS_ARB; +import static org.lwjgl.opengl.ARBShaderObjects.glAttachObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glCompileShaderARB; +import static org.lwjgl.opengl.ARBShaderObjects.glCreateProgramObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glCreateShaderObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glDeleteObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glGetInfoLogARB; +import static org.lwjgl.opengl.ARBShaderObjects.glGetObjectParameteriARB; +import static org.lwjgl.opengl.ARBShaderObjects.glLinkProgramARB; +import static org.lwjgl.opengl.ARBShaderObjects.glShaderSourceARB; +import static org.lwjgl.opengl.ARBShaderObjects.glUseProgramObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glValidateProgramARB; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.lwjgl.opengl.ARBShaderObjects; +import org.lwjgl.opengl.ARBVertexShader; +import org.lwjgl.opengl.GL11; +import org.lwjgl.util.vector.Matrix4f; + +public class ShaderProgram { + + int programID; + + public ShaderProgram() { + programID = glCreateProgramObjectARB(); + if (programID == 0) throw new RuntimeException("Unable to allocate shader program object."); + } + + public void attach(int shaderType, String resource) { + InputStream stream = ShaderProgram.class.getResourceAsStream(resource); + if (stream == null) throw new RuntimeException("Unable to locate resource: " + resource); + + attach(shaderType, stream); + } + + public void use() { + glUseProgramObjectARB(programID); + } + + public static void restore() { + glUseProgramObjectARB(0); + } + + public void link() { + glLinkProgramARB(programID); + if (glGetObjectParameteriARB(programID, GL_OBJECT_LINK_STATUS_ARB) == GL11.GL_FALSE) + throw new RuntimeException("Error linking program: " + getInfoLog(programID)); + + glValidateProgramARB(programID); + if (glGetObjectParameteriARB(programID, GL_OBJECT_VALIDATE_STATUS_ARB) == GL11.GL_FALSE) + throw new RuntimeException("Error validating program: " + getInfoLog(programID)); + + use(); + onLink(); + restore(); + } + + public void attach(int shaderType, InputStream stream) { + if (stream == null) throw new RuntimeException("Invalid shader inputstream"); + + int shaderID = 0; + try { + shaderID = glCreateShaderObjectARB(shaderType); + if (shaderID == 0) throw new RuntimeException("Unable to allocate shader object."); + + try { + glShaderSourceARB(shaderID, asString(stream)); + } catch (IOException e) { + throw new RuntimeException("Error reading inputstream.", e); + } + + glCompileShaderARB(shaderID); + if (glGetObjectParameteriARB(shaderID, GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE) + throw new RuntimeException("Error compiling shader: " + getInfoLog(shaderID)); + + glAttachObjectARB(programID, shaderID); + } catch (RuntimeException e) { + glDeleteObjectARB(shaderID); + throw e; + } + } + + public static String asString(InputStream stream) throws IOException { + StringBuilder sb = new StringBuilder(); + BufferedReader bin = new BufferedReader(new InputStreamReader(stream)); + String line; + while ((line = bin.readLine()) != null) sb.append(line).append('\n'); + stream.close(); + return sb.toString(); + } + + private static String getInfoLog(int shaderID) { + return glGetInfoLogARB(shaderID, glGetObjectParameteriARB(shaderID, GL_OBJECT_INFO_LOG_LENGTH_ARB)); + } + + public int getUniformLoc(String name) { + return ARBShaderObjects.glGetUniformLocationARB(programID, name); + } + + public int getAttribLoc(String name) { + return ARBVertexShader.glGetAttribLocationARB(programID, name); + } + + public void uniformTexture(String name, int textureIndex) { + ARBShaderObjects.glUniform1iARB(getUniformLoc(name), textureIndex); + } + + public void onLink() {} + + public void glVertexAttributeMat4(int loc, Matrix4f matrix) { + ARBVertexShader.glVertexAttrib4fARB(loc, matrix.m00, matrix.m01, matrix.m02, matrix.m03); + ARBVertexShader.glVertexAttrib4fARB(loc + 1, matrix.m10, matrix.m11, matrix.m12, matrix.m13); + ARBVertexShader.glVertexAttrib4fARB(loc + 2, matrix.m20, matrix.m21, matrix.m22, matrix.m23); + ARBVertexShader.glVertexAttrib4fARB(loc + 3, matrix.m30, matrix.m31, matrix.m32, matrix.m33); + } +} diff --git a/src/main/java/codechicken/lib/render/SpriteSheetManager.java b/src/main/java/codechicken/lib/render/SpriteSheetManager.java new file mode 100644 index 0000000..2dedc86 --- /dev/null +++ b/src/main/java/codechicken/lib/render/SpriteSheetManager.java @@ -0,0 +1,129 @@ +package codechicken.lib.render; + +import java.util.ArrayList; +import java.util.HashMap; + +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.util.IIcon; +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.render.TextureUtils.IIconSelfRegister; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class SpriteSheetManager { + + @SideOnly(Side.CLIENT) + public static class SpriteSheet implements IIconSelfRegister { + + private int tilesX; + private int tilesY; + private ArrayList newSprites = new ArrayList(); + private TextureSpecial[] sprites; + private ResourceLocation resource; + private TextureDataHolder texture; + private int spriteWidth; + private int spriteHeight; + + public int atlasIndex; + + private SpriteSheet(int tilesX, int tilesY, ResourceLocation textureFile) { + this.tilesX = tilesX; + this.tilesY = tilesY; + this.resource = textureFile; + sprites = new TextureSpecial[tilesX * tilesY]; + } + + public void requestIndicies(int... indicies) { + for (int i : indicies) setupSprite(i); + } + + public void registerIcons(IIconRegister register) { + TextureMap textureMap = (TextureMap) register; + + if (TextureUtils.refreshTexture(textureMap, resource.getResourcePath())) { + reloadTexture(); + for (TextureSpecial sprite : sprites) + if (sprite != null) textureMap.setTextureEntry(sprite.getIconName(), sprite); + } else { + for (int i : newSprites) textureMap.setTextureEntry(sprites[i].getIconName(), sprites[i]); + } + newSprites.clear(); + } + + public TextureSpecial setupSprite(int i) { + if (sprites[i] == null) { + String name = resource + "_" + i; + sprites[i] = new TextureSpecial(name).baseFromSheet(this, i); + newSprites.add(i); + } + return sprites[i]; + } + + private void reloadTexture() { + texture = TextureUtils.loadTexture(resource); + spriteWidth = texture.width / tilesX; + spriteHeight = texture.height / tilesY; + } + + public IIcon getSprite(int index) { + IIcon i = sprites[index]; + if (i == null) throw new IllegalArgumentException( + "Sprite at index: " + index + " from texture file " + resource + " was not preloaded."); + return i; + } + + public TextureDataHolder createSprite(int spriteIndex) { + int sx = spriteIndex % tilesX; + int sy = spriteIndex / tilesX; + TextureDataHolder sprite = new TextureDataHolder(spriteWidth, spriteHeight); + TextureUtils.copySubImg( + texture.data, + texture.width, + sx * spriteWidth, + sy * spriteHeight, + spriteWidth, + spriteHeight, + sprite.data, + spriteWidth, + 0, + 0); + return sprite; + } + + public int spriteWidth() { + return spriteWidth; + } + + public int spriteHeight() { + return spriteHeight; + } + + public TextureSpecial bindTextureFX(int i, TextureFX textureFX) { + return setupSprite(i).addTextureFX(textureFX); + } + + public SpriteSheet selfRegister(int atlas) { + TextureUtils.addIconRegistrar(this); + return this; + } + + @Override + public int atlasIndex() { + return atlasIndex; + } + } + + private static HashMap spriteSheets = new HashMap(); + + public static SpriteSheet getSheet(ResourceLocation resource) { + return getSheet(16, 16, resource); + } + + public static SpriteSheet getSheet(int tilesX, int tilesY, ResourceLocation resource) { + SpriteSheet sheet = spriteSheets.get(resource.toString()); + if (sheet == null) spriteSheets.put(resource.toString(), sheet = new SpriteSheet(tilesX, tilesY, resource)); + return sheet; + } +} diff --git a/src/main/java/codechicken/lib/render/TextureDataHolder.java b/src/main/java/codechicken/lib/render/TextureDataHolder.java new file mode 100644 index 0000000..fd5dccf --- /dev/null +++ b/src/main/java/codechicken/lib/render/TextureDataHolder.java @@ -0,0 +1,34 @@ +package codechicken.lib.render; + +import java.awt.image.BufferedImage; + +public class TextureDataHolder { + + public int width; + public int height; + public int[] data; + + public TextureDataHolder(int width, int height) { + this.width = width; + this.height = height; + data = new int[width * height]; + } + + public TextureDataHolder(int[] data, int width) { + this.data = data; + this.width = width; + height = data.length / width; + } + + public TextureDataHolder(BufferedImage img) { + this(img.getWidth(), img.getHeight()); + img.getRGB(0, 0, width, height, data, 0, width); + } + + public TextureDataHolder copyData() { + int[] copy = new int[data.length]; + System.arraycopy(data, 0, copy, 0, data.length); + data = copy; + return this; + } +} diff --git a/src/main/java/codechicken/lib/render/TextureFX.java b/src/main/java/codechicken/lib/render/TextureFX.java new file mode 100644 index 0000000..d1cf852 --- /dev/null +++ b/src/main/java/codechicken/lib/render/TextureFX.java @@ -0,0 +1,59 @@ +package codechicken.lib.render; + +import net.minecraft.client.Minecraft; + +import codechicken.lib.render.SpriteSheetManager.SpriteSheet; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class TextureFX { + + public int[] imageData; + public int tileSizeBase = 16; + public int tileSizeSquare = 256; + public int tileSizeMask = 15; + public int tileSizeSquareMask = 255; + + public boolean anaglyphEnabled; + public TextureSpecial texture; + + public TextureFX(int spriteIndex, SpriteSheet sheet) { + texture = sheet.bindTextureFX(spriteIndex, this); + } + + public TextureFX(int size, String name) { + texture = new TextureSpecial(name).blank(size).selfRegister().addTextureFX(this); + } + + public TextureFX setAtlas(int index) { + texture.atlasIndex = index; + return this; + } + + public void setup() { + imageData = new int[tileSizeSquare]; + } + + public void onTextureDimensionsUpdate(int width, int height) { + if (width != height) + throw new IllegalArgumentException("Non-Square textureFX not supported (" + width + ":" + height + ")"); + + tileSizeBase = width; + tileSizeSquare = tileSizeBase * tileSizeBase; + tileSizeMask = tileSizeBase - 1; + tileSizeSquareMask = tileSizeSquare - 1; + setup(); + } + + public void update() { + anaglyphEnabled = Minecraft.getMinecraft().gameSettings.anaglyph; + onTick(); + } + + public void onTick() {} + + public boolean changed() { + return true; + } +} diff --git a/src/main/java/codechicken/lib/render/TextureSpecial.java b/src/main/java/codechicken/lib/render/TextureSpecial.java new file mode 100644 index 0000000..ee797c7 --- /dev/null +++ b/src/main/java/codechicken/lib/render/TextureSpecial.java @@ -0,0 +1,180 @@ +package codechicken.lib.render; + +import java.awt.image.BufferedImage; +import java.util.ArrayList; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.data.AnimationMetadataSection; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.render.SpriteSheetManager.SpriteSheet; +import codechicken.lib.render.TextureUtils.IIconSelfRegister; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class TextureSpecial extends TextureAtlasSprite implements IIconSelfRegister { + + // sprite sheet fields + private int spriteIndex; + private SpriteSheet spriteSheet; + + // textureFX fields + private TextureFX textureFX; + private int mipmapLevels; + private int rawWidth; + private int rawHeight; + + private int blankSize = -1; + private ArrayList baseTextures; + + private boolean selfRegister; + public int atlasIndex; + + protected TextureSpecial(String par1) { + super(par1); + } + + public TextureSpecial addTexture(TextureDataHolder t) { + if (baseTextures == null) baseTextures = new ArrayList(); + baseTextures.add(t); + return this; + } + + public TextureSpecial baseFromSheet(SpriteSheet spriteSheet, int spriteIndex) { + this.spriteSheet = spriteSheet; + this.spriteIndex = spriteIndex; + return this; + } + + public TextureSpecial addTextureFX(TextureFX fx) { + textureFX = fx; + return this; + } + + @Override + public void initSprite(int sheetWidth, int sheetHeight, int originX, int originY, boolean rotated) { + super.initSprite(sheetWidth, sheetHeight, originX, originY, rotated); + if (textureFX != null) textureFX.onTextureDimensionsUpdate(rawWidth, rawHeight); + } + + @Override + public void updateAnimation() { + if (textureFX != null) { + textureFX.update(); + if (textureFX.changed()) { + int[][] mipmaps = new int[mipmapLevels + 1][]; + mipmaps[0] = textureFX.imageData; + mipmaps = prepareAnisotropicFiltering(mipmaps); + mipmaps = TextureUtil.generateMipmapData(mipmapLevels, width, mipmaps); + TextureUtil.uploadTextureMipmap(mipmaps, width, height, originX, originY, false, false); + } + } + } + + /** + * Copy paste mojang code because it's private, and CCL can't have access transformers or reflection + */ + public int[][] prepareAnisotropicFiltering(int[][] mipmaps) { + if (Minecraft.getMinecraft().gameSettings.anisotropicFiltering <= 1) { + return mipmaps; + } else { + int[][] aint1 = new int[mipmaps.length][]; + + for (int k = 0; k < mipmaps.length; ++k) { + int[] aint2 = mipmaps[k]; + + if (aint2 != null) { + int[] aint3 = new int[(rawWidth + 16 >> k) * (rawHeight + 16 >> k)]; + System.arraycopy(aint2, 0, aint3, 0, aint2.length); + aint1[k] = TextureUtil.prepareAnisotropicData(aint3, rawWidth >> k, rawHeight >> k, 8 >> k); + } + } + + return aint1; + } + } + + @Override + public void loadSprite(BufferedImage[] images, AnimationMetadataSection animationMeta, + boolean anisotropicFiltering) { + rawWidth = images[0].getWidth(); + rawHeight = images[0].getHeight(); + super.loadSprite(images, animationMeta, anisotropicFiltering); + } + + @Override + public void generateMipmaps(int p_147963_1_) { + super.generateMipmaps(p_147963_1_); + mipmapLevels = p_147963_1_; + } + + @Override + public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) { + return true; + } + + public void addFrame(int[] data, int width, int height) { + GameSettings settings = Minecraft.getMinecraft().gameSettings; + BufferedImage[] images = new BufferedImage[settings.mipmapLevels + 1]; + images[0] = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + images[0].setRGB(0, 0, width, height, data, 0, width); + + loadSprite(images, null, settings.anisotropicFiltering > 1); + } + + @Override + public boolean load(IResourceManager manager, ResourceLocation location) { + if (baseTextures != null) { + for (TextureDataHolder tex : baseTextures) addFrame(tex.data, tex.width, tex.height); + } else if (spriteSheet != null) { + TextureDataHolder tex = spriteSheet.createSprite(spriteIndex); + addFrame(tex.data, tex.width, tex.height); + } else if (blankSize > 0) { + addFrame(new int[blankSize * blankSize], blankSize, blankSize); + } + + if (framesTextureData.isEmpty()) throw new RuntimeException("No base frame for texture: " + getIconName()); + + return false; + } + + @Override + public boolean hasAnimationMetadata() { + return textureFX != null || super.hasAnimationMetadata(); + } + + @Override + public int getFrameCount() { + if (textureFX != null) return 1; + + return super.getFrameCount(); + } + + public TextureSpecial blank(int size) { + blankSize = size; + return this; + } + + public TextureSpecial selfRegister() { + selfRegister = true; + TextureUtils.addIconRegistrar(this); + return this; + } + + @Override + public void registerIcons(IIconRegister register) { + if (selfRegister) ((TextureMap) register).setTextureEntry(getIconName(), this); + } + + @Override + public int atlasIndex() { + return atlasIndex; + } +} diff --git a/src/main/java/codechicken/lib/render/TextureUtils.java b/src/main/java/codechicken/lib/render/TextureUtils.java new file mode 100644 index 0000000..b7ab4bb --- /dev/null +++ b/src/main/java/codechicken/lib/render/TextureUtils.java @@ -0,0 +1,168 @@ +package codechicken.lib.render; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +import javax.imageio.ImageIO; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.util.IIcon; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.common.MinecraftForge; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import codechicken.lib.colour.Colour; +import codechicken.lib.colour.ColourARGB; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; + +public class TextureUtils { + + public static interface IIconSelfRegister { + + public void registerIcons(IIconRegister register); + + public int atlasIndex(); + } + + static { + MinecraftForge.EVENT_BUS.register(new TextureUtils()); + } + + private static ArrayList iconRegistrars = new ArrayList(); + + public static void addIconRegistrar(IIconSelfRegister registrar) { + iconRegistrars.add(registrar); + } + + @SubscribeEvent + public void textureLoad(TextureStitchEvent.Pre event) { + for (IIconSelfRegister reg : iconRegistrars) + if (reg.atlasIndex() == event.map.getTextureType()) reg.registerIcons(event.map); + } + + /** + * @return an array of ARGB pixel data + */ + public static int[] loadTextureData(ResourceLocation resource) { + return loadTexture(resource).data; + } + + public static Colour[] loadTextureColours(ResourceLocation resource) { + int[] idata = loadTextureData(resource); + Colour[] data = new Colour[idata.length]; + for (int i = 0; i < data.length; i++) data[i] = new ColourARGB(idata[i]); + return data; + } + + public static InputStream getTextureResource(ResourceLocation textureFile) throws IOException { + return Minecraft.getMinecraft().getResourceManager().getResource(textureFile).getInputStream(); + } + + public static BufferedImage loadBufferedImage(ResourceLocation textureFile) { + try { + return loadBufferedImage(getTextureResource(textureFile)); + } catch (Exception e) { + System.err.println("Failed to load texture file: " + textureFile); + e.printStackTrace(); + } + return null; + } + + public static BufferedImage loadBufferedImage(InputStream in) throws IOException { + BufferedImage img = ImageIO.read(in); + in.close(); + return img; + } + + public static TextureManager engine() { + return Minecraft.getMinecraft().renderEngine; + } + + public static void copySubImg(int[] fromTex, int fromWidth, int fromX, int fromY, int width, int height, + int[] toTex, int toWidth, int toX, int toY) { + for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) { + int fp = (y + fromY) * fromWidth + x + fromX; + int tp = (y + toX) * toWidth + x + toX; + + toTex[tp] = fromTex[fp]; + } + } + + public static void bindAtlas(int atlasIndex) { + engine().bindTexture(atlasIndex == 0 ? TextureMap.locationBlocksTexture : TextureMap.locationItemsTexture); + } + + public static IIcon getBlankIcon(int size, IIconRegister iconRegister) { + TextureMap textureMap = (TextureMap) iconRegister; + String s = "blank_" + size; + if (textureMap.getTextureExtry(s) == null) { + TextureSpecial icon = new TextureSpecial(s).blank(size); + textureMap.setTextureEntry(s, icon); + } + return iconRegister.registerIcon(s); + } + + public static TextureSpecial getTextureSpecial(IIconRegister iconRegister, String name) { + TextureMap textureMap = (TextureMap) iconRegister; + IIcon entry = textureMap.getTextureExtry(name); + if (entry != null) throw new IllegalStateException("Texture: " + name + " is already registered"); + + TextureSpecial icon = new TextureSpecial(name); + textureMap.setTextureEntry(name, icon); + return icon; + } + + public static void prepareTexture(int target, int texture, int min_mag_filter, int wrap) { + GL11.glBindTexture(target, texture); + GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, min_mag_filter); + GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, min_mag_filter); + switch (target) { + case GL12.GL_TEXTURE_3D: + GL11.glTexParameteri(target, GL12.GL_TEXTURE_WRAP_R, wrap); + case GL11.GL_TEXTURE_2D: + GL11.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, wrap); + case GL11.GL_TEXTURE_1D: + GL11.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, wrap); + } + } + + public static TextureDataHolder loadTexture(ResourceLocation resource) { + BufferedImage img = loadBufferedImage(resource); + if (img == null) throw new RuntimeException("Texture not found: " + resource); + return new TextureDataHolder(img); + } + + /** + * Uses an empty placeholder texture to tell if the map has been reloaded since the last call to refresh texture and + * the texture with name needs to be reacquired to be valid + */ + public static boolean refreshTexture(TextureMap map, String name) { + if (map.getTextureExtry(name) == null) { + map.setTextureEntry(name, new PlaceholderTexture(name)); + return true; + } + return false; + } + + public static IIcon safeIcon(IIcon icon) { + if (icon == null) + icon = ((TextureMap) engine().getTexture(TextureMap.locationBlocksTexture)).getAtlasSprite("missingno"); + + return icon; + } + + public static boolean isMissing(IIcon icon, ResourceLocation atlas) { + if (icon == null) return true; + + IIcon missing = ((TextureMap) engine().getTexture(atlas)).getAtlasSprite("missingno"); + return icon.getMinU() == missing.getMinU() && icon.getMinV() == missing.getMinV(); + } +} diff --git a/src/main/java/codechicken/lib/render/Vertex5.java b/src/main/java/codechicken/lib/render/Vertex5.java new file mode 100644 index 0000000..6c5156b --- /dev/null +++ b/src/main/java/codechicken/lib/render/Vertex5.java @@ -0,0 +1,91 @@ +package codechicken.lib.render; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.render.uv.UV; +import codechicken.lib.render.uv.UVTransformation; +import codechicken.lib.util.Copyable; +import codechicken.lib.vec.Transformation; +import codechicken.lib.vec.Vector3; + +public class Vertex5 implements Copyable { + + public Vector3 vec; + public UV uv; + + public Vertex5() { + this(new Vector3(), new UV()); + } + + public Vertex5(Vector3 vert, UV uv) { + this.vec = vert; + this.uv = uv; + } + + public Vertex5(Vector3 vert, double u, double v) { + this(vert, new UV(u, v)); + } + + public Vertex5(double x, double y, double z, double u, double v) { + this(x, y, z, u, v, 0); + } + + public Vertex5(double x, double y, double z, double u, double v, int tex) { + this(new Vector3(x, y, z), new UV(u, v, tex)); + } + + public Vertex5 set(double x, double y, double z, double u, double v) { + vec.set(x, y, z); + uv.set(u, v); + return this; + } + + public Vertex5 set(double x, double y, double z, double u, double v, int tex) { + vec.set(x, y, z); + uv.set(u, v, tex); + return this; + } + + public Vertex5 set(Vertex5 vert) { + vec.set(vert.vec); + uv.set(vert.uv); + return this; + } + + public Vertex5(Vertex5 vertex5) { + this(vertex5.vec.copy(), vertex5.uv.copy()); + } + + public Vertex5 copy() { + return new Vertex5(this); + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Vertex: (" + new BigDecimal(vec.x, cont) + + ", " + + new BigDecimal(vec.y, cont) + + ", " + + new BigDecimal(vec.z, cont) + + ") " + + "(" + + new BigDecimal(uv.u, cont) + + ", " + + new BigDecimal(uv.v, cont) + + ") (" + + uv.tex + + ")"; + } + + public Vertex5 apply(Transformation t) { + vec.apply(t); + return this; + } + + public Vertex5 apply(UVTransformation t) { + uv.apply(t); + return this; + } +} diff --git a/src/main/java/codechicken/lib/render/uv/IconTransformation.java b/src/main/java/codechicken/lib/render/uv/IconTransformation.java new file mode 100644 index 0000000..af19b26 --- /dev/null +++ b/src/main/java/codechicken/lib/render/uv/IconTransformation.java @@ -0,0 +1,25 @@ +package codechicken.lib.render.uv; + +import net.minecraft.util.IIcon; + +import codechicken.lib.vec.IrreversibleTransformationException; + +public class IconTransformation extends UVTransformation { + + public IIcon icon; + + public IconTransformation(IIcon icon) { + this.icon = icon; + } + + @Override + public void apply(UV uv) { + uv.u = icon.getInterpolatedU(uv.u * 16); + uv.v = icon.getInterpolatedV(uv.v * 16); + } + + @Override + public UVTransformation inverse() { + throw new IrreversibleTransformationException(this); + } +} diff --git a/src/main/java/codechicken/lib/render/uv/MultiIconTransformation.java b/src/main/java/codechicken/lib/render/uv/MultiIconTransformation.java new file mode 100644 index 0000000..d0521a8 --- /dev/null +++ b/src/main/java/codechicken/lib/render/uv/MultiIconTransformation.java @@ -0,0 +1,26 @@ +package codechicken.lib.render.uv; + +import net.minecraft.util.IIcon; + +import codechicken.lib.vec.IrreversibleTransformationException; + +public class MultiIconTransformation extends UVTransformation { + + public IIcon[] icons; + + public MultiIconTransformation(IIcon... icons) { + this.icons = icons; + } + + @Override + public void apply(UV uv) { + IIcon icon = icons[uv.tex % icons.length]; + uv.u = icon.getInterpolatedU(uv.u * 16); + uv.v = icon.getInterpolatedV(uv.v * 16); + } + + @Override + public UVTransformation inverse() { + throw new IrreversibleTransformationException(this); + } +} diff --git a/src/main/java/codechicken/lib/render/uv/UV.java b/src/main/java/codechicken/lib/render/uv/UV.java new file mode 100644 index 0000000..e4828db --- /dev/null +++ b/src/main/java/codechicken/lib/render/uv/UV.java @@ -0,0 +1,78 @@ +package codechicken.lib.render.uv; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.util.Copyable; + +public class UV implements Copyable { + + public double u; + public double v; + public int tex; + + public UV() {} + + public UV(double u, double v) { + this(u, v, 0); + } + + public UV(double u, double v, int tex) { + this.u = u; + this.v = v; + this.tex = tex; + } + + public UV(UV uv) { + this(uv.u, uv.v, uv.tex); + } + + public UV set(double u, double v, int tex) { + this.u = u; + this.v = v; + this.tex = tex; + return this; + } + + public UV set(double u, double v) { + return set(u, v, tex); + } + + public UV set(UV uv) { + return set(uv.u, uv.v, uv.tex); + } + + public UV copy() { + return new UV(this); + } + + public UV add(UV uv) { + u += uv.u; + v += uv.v; + return this; + } + + public UV multiply(double d) { + u *= d; + v *= d; + return this; + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "UV(" + new BigDecimal(u, cont) + ", " + new BigDecimal(v, cont) + ")"; + } + + public UV apply(UVTransformation t) { + t.apply(this); + return this; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof UV)) return false; + UV uv = (UV) o; + return u == uv.u && v == uv.v; + } +} diff --git a/src/main/java/codechicken/lib/render/uv/UVRotation.java b/src/main/java/codechicken/lib/render/uv/UVRotation.java new file mode 100644 index 0000000..6935603 --- /dev/null +++ b/src/main/java/codechicken/lib/render/uv/UVRotation.java @@ -0,0 +1,51 @@ +package codechicken.lib.render.uv; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.math.MathHelper; + +public class UVRotation extends UVTransformation { + + public double angle; + + /** + * @param angle The angle to rotate counterclockwise in radians + */ + public UVRotation(double angle) { + this.angle = angle; + } + + @Override + public void apply(UV uv) { + double c = MathHelper.cos(angle); + double s = MathHelper.sin(angle); + double u2 = c * uv.u + s * uv.v; + uv.v = -s * uv.u + c * uv.v; + uv.u = u2; + } + + @Override + public UVTransformation inverse() { + return new UVRotation(-angle); + } + + @Override + public UVTransformation merge(UVTransformation next) { + if (next instanceof UVRotation) return new UVRotation(angle + ((UVRotation) next).angle); + + return null; + } + + @Override + public boolean isRedundant() { + return MathHelper.between(-1E-5, angle, 1E-5); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "UVRotation(" + new BigDecimal(angle, cont) + ")"; + } +} diff --git a/src/main/java/codechicken/lib/render/uv/UVScale.java b/src/main/java/codechicken/lib/render/uv/UVScale.java new file mode 100644 index 0000000..a2de0ef --- /dev/null +++ b/src/main/java/codechicken/lib/render/uv/UVScale.java @@ -0,0 +1,37 @@ +package codechicken.lib.render.uv; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +public class UVScale extends UVTransformation { + + double su; + double sv; + + public UVScale(double scaleu, double scalev) { + su = scaleu; + sv = scalev; + } + + public UVScale(double d) { + this(d, d); + } + + @Override + public void apply(UV uv) { + uv.u *= su; + uv.v *= sv; + } + + @Override + public UVTransformation inverse() { + return new UVScale(1 / su, 1 / sv); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "UVScale(" + new BigDecimal(su, cont) + ", " + new BigDecimal(sv, cont) + ")"; + } +} diff --git a/src/main/java/codechicken/lib/render/uv/UVTransformation.java b/src/main/java/codechicken/lib/render/uv/UVTransformation.java new file mode 100644 index 0000000..d7ca168 --- /dev/null +++ b/src/main/java/codechicken/lib/render/uv/UVTransformation.java @@ -0,0 +1,39 @@ +package codechicken.lib.render.uv; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.ITransformation; + +/** + * Abstract supertype for any UV transformation + */ +public abstract class UVTransformation extends ITransformation + implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + + public UVTransformation at(UV point) { + return new UVTransformationList( + new UVTranslation(-point.u, -point.v), + this, + new UVTranslation(point.u, point.v)); + } + + public UVTransformationList with(UVTransformation t) { + return new UVTransformationList(this, t); + } + + @Override + public boolean load(CCRenderState state) { + return !isRedundant(); + } + + @Override + public void operate(CCRenderState state) { + apply(state.vert.uv); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/src/main/java/codechicken/lib/render/uv/UVTransformationList.java b/src/main/java/codechicken/lib/render/uv/UVTransformationList.java new file mode 100644 index 0000000..745b22d --- /dev/null +++ b/src/main/java/codechicken/lib/render/uv/UVTransformationList.java @@ -0,0 +1,83 @@ +package codechicken.lib.render.uv; + +import java.util.ArrayList; +import java.util.Iterator; + +public class UVTransformationList extends UVTransformation { + + private ArrayList transformations = new ArrayList(); + + public UVTransformationList(UVTransformation... transforms) { + for (UVTransformation t : transforms) + if (t instanceof UVTransformationList) transformations.addAll(((UVTransformationList) t).transformations); + else transformations.add(t); + + compact(); + } + + @Override + public void apply(UV uv) { + for (int i = 0; i < transformations.size(); i++) transformations.get(i).apply(uv); + } + + @Override + public UVTransformationList with(UVTransformation t) { + if (t.isRedundant()) return this; + + if (t instanceof UVTransformationList) transformations.addAll(((UVTransformationList) t).transformations); + else transformations.add(t); + + compact(); + return this; + } + + public UVTransformationList prepend(UVTransformation t) { + if (t.isRedundant()) return this; + + if (t instanceof UVTransformationList) transformations.addAll(0, ((UVTransformationList) t).transformations); + else transformations.add(0, t); + + compact(); + return this; + } + + private void compact() { + ArrayList newList = new ArrayList(transformations.size()); + Iterator iterator = transformations.iterator(); + UVTransformation prev = null; + while (iterator.hasNext()) { + UVTransformation t = iterator.next(); + if (t.isRedundant()) continue; + + if (prev != null) { + UVTransformation m = prev.merge(t); + if (m == null) newList.add(prev); + else if (m.isRedundant()) t = null; + else t = m; + } + prev = t; + } + if (prev != null) newList.add(prev); + + if (newList.size() < transformations.size()) transformations = newList; + } + + @Override + public boolean isRedundant() { + return transformations.size() == 0; + } + + @Override + public UVTransformation inverse() { + UVTransformationList rev = new UVTransformationList(); + for (int i = transformations.size() - 1; i >= 0; i--) rev.with(transformations.get(i).inverse()); + return rev; + } + + @Override + public String toString() { + String s = ""; + for (UVTransformation t : transformations) s += "\n" + t.toString(); + return s.trim(); + } +} diff --git a/src/main/java/codechicken/lib/render/uv/UVTranslation.java b/src/main/java/codechicken/lib/render/uv/UVTranslation.java new file mode 100644 index 0000000..2daf58f --- /dev/null +++ b/src/main/java/codechicken/lib/render/uv/UVTranslation.java @@ -0,0 +1,55 @@ +package codechicken.lib.render.uv; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.math.MathHelper; + +public class UVTranslation extends UVTransformation { + + public double du; + public double dv; + + public UVTranslation(double u, double v) { + du = u; + dv = v; + } + + @Override + public void apply(UV uv) { + uv.u += du; + uv.v += dv; + } + + @Override + public UVTransformation at(UV point) { + return this; + } + + @Override + public UVTransformation inverse() { + return new UVTranslation(-du, -dv); + } + + @Override + public UVTransformation merge(UVTransformation next) { + if (next instanceof UVTranslation) { + UVTranslation t = (UVTranslation) next; + return new UVTranslation(du + t.du, dv + t.dv); + } + + return null; + } + + @Override + public boolean isRedundant() { + return MathHelper.between(-1E-5, du, 1E-5) && MathHelper.between(-1E-5, dv, 1E-5); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "UVTranslation(" + new BigDecimal(du, cont) + ", " + new BigDecimal(dv, cont) + ")"; + } +} diff --git a/src/main/java/codechicken/lib/tool/LibDownloader.java b/src/main/java/codechicken/lib/tool/LibDownloader.java new file mode 100644 index 0000000..ca4a61f --- /dev/null +++ b/src/main/java/codechicken/lib/tool/LibDownloader.java @@ -0,0 +1,100 @@ +package codechicken.lib.tool; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; + +public class LibDownloader { + + private static String[] libs = new String[] { "org/ow2/asm/asm-debug-all/5.0.3/asm-debug-all-5.0.3.jar", + "com/google/guava/guava/14.0/guava-14.0.jar", "net/sf/jopt-simple/jopt-simple/4.5/jopt-simple-4.5.jar", + "org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar", + "org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar" }; + private static File libDir = new File("lib"); + + private static ByteBuffer downloadBuffer = ByteBuffer.allocateDirect(1 << 23); + + public static void load() { + if (!libDir.exists()) libDir.mkdir(); + if (!libDir.isDirectory()) throw new RuntimeException("/lib is not a directory"); + + List missing = checkExists(); + for (String lib : missing) download(lib); + addPaths(libs); + } + + private static void addPaths(String[] libs) { + try { + URLClassLoader cl = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Method m_addURL = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class }); + m_addURL.setAccessible(true); + for (String lib : libs) m_addURL.invoke(cl, new File(libDir, fileName(lib)).toURI().toURL()); + } catch (Exception e) { + throw new RuntimeException("Failed to add libraries to classpath", e); + } + } + + private static void download(String lib) { + File libFile = new File(libDir, fileName(lib)); + try { + URL libDownload = new URL("http://repo1.maven.org/maven2/" + lib); + URLConnection connection = libDownload.openConnection(); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.setRequestProperty("User-Agent", "CodeChickenLib Downloader"); + int sizeGuess = connection.getContentLength(); + download(connection.getInputStream(), sizeGuess, libFile); + } catch (Exception e) { + libFile.delete(); + throw new RuntimeException("A download error occured", e); + } + } + + private static void download(InputStream is, int sizeGuess, File target) throws Exception { + String name = target.getName(); + if (sizeGuess > downloadBuffer.capacity()) + throw new Exception(String.format("The file %s is too large to be downloaded", name)); + + downloadBuffer.clear(); + + int bytesRead, fullLength = 0; + + System.out.format("Downloading lib %s", name); + byte[] smallBuffer = new byte[1024]; + while ((bytesRead = is.read(smallBuffer)) >= 0) { + downloadBuffer.put(smallBuffer, 0, bytesRead); + fullLength += bytesRead; + System.out.format("\rDownloading lib %s %d%%", name, (int) (fullLength * 100 / sizeGuess)); + } + System.out.format("\rDownloaded lib %s \n", name); + is.close(); + downloadBuffer.limit(fullLength); + + if (!target.exists()) target.createNewFile(); + + downloadBuffer.position(0); + FileOutputStream fos = new FileOutputStream(target); + fos.getChannel().write(downloadBuffer); + fos.close(); + } + + private static String fileName(String lib) { + return lib.replaceAll(".+/", ""); + } + + private static List checkExists() { + LinkedList list = new LinkedList(); + for (String lib : libs) { + File file = new File(libDir, fileName(lib)); + if (!file.exists()) list.add(lib); + } + return list; + } +} diff --git a/src/main/java/codechicken/lib/tool/MCStripTransformer.java b/src/main/java/codechicken/lib/tool/MCStripTransformer.java new file mode 100644 index 0000000..5c8e47b --- /dev/null +++ b/src/main/java/codechicken/lib/tool/MCStripTransformer.java @@ -0,0 +1,45 @@ +package codechicken.lib.tool; + +import java.util.Iterator; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.commons.RemappingMethodAdapter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import codechicken.lib.asm.ASMHelper; + +public class MCStripTransformer { + + public static class ReferenceDetector extends Remapper { + + boolean found = false; + + @Override + public String map(String typeName) { + if (typeName.startsWith("net/minecraft") || !typeName.contains("/")) found = true; + return typeName; + } + } + + public static byte[] transform(byte[] bytes) { + ClassNode cnode = ASMHelper.createClassNode(bytes, ClassReader.EXPAND_FRAMES); + + boolean changed = false; + Iterator it = cnode.methods.iterator(); + while (it.hasNext()) { + MethodNode mnode = it.next(); + ReferenceDetector r = new ReferenceDetector(); + mnode.accept(new RemappingMethodAdapter(mnode.access, mnode.desc, new MethodVisitor(Opcodes.ASM4) {}, r)); + if (r.found) { + it.remove(); + changed = true; + } + } + if (changed) bytes = ASMHelper.createBytes(cnode, 0); + return bytes; + } +} diff --git a/src/main/java/codechicken/lib/tool/Main.java b/src/main/java/codechicken/lib/tool/Main.java new file mode 100644 index 0000000..039121c --- /dev/null +++ b/src/main/java/codechicken/lib/tool/Main.java @@ -0,0 +1,14 @@ +package codechicken.lib.tool; + +public class Main { + + public static void main(String[] args) { + LibDownloader.load(); + try { + Class c_toolMain = new StripClassLoader().loadClass("codechicken.lib.tool.ToolMain"); + c_toolMain.getDeclaredMethod("main", String[].class).invoke(null, new Object[] { args }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/codechicken/lib/tool/StripClassLoader.java b/src/main/java/codechicken/lib/tool/StripClassLoader.java new file mode 100644 index 0000000..92e334b --- /dev/null +++ b/src/main/java/codechicken/lib/tool/StripClassLoader.java @@ -0,0 +1,47 @@ +package codechicken.lib.tool; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; + +public class StripClassLoader extends URLClassLoader { + + public StripClassLoader() { + super(new URL[0], StripClassLoader.class.getClassLoader()); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (!name.startsWith("codechicken.lib")) return super.loadClass(name); + + try { + String resName = name.replace('.', '/') + ".class"; + InputStream res = getResourceAsStream(resName); + if (res == null) throw new ClassNotFoundException("Could not find resource: " + resName); + byte[] bytes = readFully(res); + bytes = transform(bytes); + return defineClass(name, bytes, 0, bytes.length); + + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } + + public static byte[] readFully(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int read; + byte[] data = new byte[16384]; + while ((read = is.read(data, 0, data.length)) > 0) buffer.write(data, 0, read); + + return buffer.toByteArray(); + } + + private byte[] transform(byte[] bytes) { + + bytes = MCStripTransformer.transform(bytes); + return bytes; + } +} diff --git a/src/main/java/codechicken/lib/tool/ToolMain.java b/src/main/java/codechicken/lib/tool/ToolMain.java new file mode 100644 index 0000000..ff51b7b --- /dev/null +++ b/src/main/java/codechicken/lib/tool/ToolMain.java @@ -0,0 +1,42 @@ +package codechicken.lib.tool; + +import codechicken.lib.tool.module.ModuleQBConverter; + +public class ToolMain { + + public static interface Module { + + public void main(String[] args); + + public String name(); + + public void printHelp(); + } + + public static Module[] modules = new Module[] { new ModuleQBConverter() }; + + private static void printHelp() { + System.out.println("Usage: [module] [args]"); + System.out.println(" Modules: "); + for (Module m : modules) System.out.println(" - " + m.name()); + System.out.println("-h [module] for module help"); + } + + public static void main(String[] args) { + if (args.length > 0) { + for (Module m : modules) if (args[0].equals(m.name())) { + String[] args2 = new String[args.length - 1]; + System.arraycopy(args, 1, args2, 0, args2.length); + m.main(args2); + return; + } + if (args[0].equals("-h") && args.length >= 2) { + for (Module m : modules) if (args[1].equals(m.name())) { + m.printHelp(); + return; + } + } + } + printHelp(); + } +} diff --git a/src/main/java/codechicken/lib/tool/module/JOptModule.java b/src/main/java/codechicken/lib/tool/module/JOptModule.java new file mode 100644 index 0000000..2c9adee --- /dev/null +++ b/src/main/java/codechicken/lib/tool/module/JOptModule.java @@ -0,0 +1,42 @@ +package codechicken.lib.tool.module; + +import java.io.IOException; + +import codechicken.lib.tool.ToolMain; +import joptsimple.OptionException; +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +public abstract class JOptModule implements ToolMain.Module { + + OptionParser parser = new OptionParser(); + + @Override + public void main(String[] args) { + OptionSet options; + try { + options = parser.parse(args); + } catch (OptionException ex) { + System.err.println(ex.getLocalizedMessage()); + System.exit(-1); + return; + } + + try { + main(parser, options); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected abstract void main(OptionParser parser, OptionSet options); + + @Override + public void printHelp() { + try { + parser.printHelpOn(System.out); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/codechicken/lib/tool/module/ModuleQBConverter.java b/src/main/java/codechicken/lib/tool/module/ModuleQBConverter.java new file mode 100644 index 0000000..f9d9bda --- /dev/null +++ b/src/main/java/codechicken/lib/tool/module/ModuleQBConverter.java @@ -0,0 +1,65 @@ +package codechicken.lib.tool.module; + +import static java.util.Arrays.asList; + +import java.io.File; + +import codechicken.lib.render.QBImporter; +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +public class ModuleQBConverter extends JOptModule { + + public ModuleQBConverter() { + parser.acceptsAll(asList("?", "h", "help"), "Show the help"); + parser.acceptsAll(asList("i", "input"), "comma separated list of paths to models (.qb or directories)") + .withRequiredArg().ofType(File.class).withValuesSeparatedBy(',').required(); + parser.acceptsAll(asList("o", "out"), "Output Directory").withRequiredArg().ofType(File.class); + parser.acceptsAll( + asList("o2", "textureplanes"), + "2nd level optimisation. Merges coplanar polygons. Increases texture size"); + parser.acceptsAll(asList("s", "squaretextures"), "Produce square textures"); + parser.acceptsAll(asList("t", "mergetextures"), "Use the same texture for all models"); + parser.acceptsAll(asList("r", "scalemc"), "Resize model to mc standard (shrink by factor of 16)"); + } + + protected void main(OptionParser parser, OptionSet options) { + int flags = 0; + if (options.has("o2")) flags |= QBImporter.TEXTUREPLANES; + if (options.has("s")) flags |= QBImporter.SQUARETEXTURE; + if (options.has("t")) flags |= QBImporter.MERGETEXTURES; + if (options.has("r")) flags |= QBImporter.SCALEMC; + + File[] input = options.valuesOf("input").toArray(new File[0]); + File[] outDir = new File[input.length]; + if (options.has("out")) { + File output = (File) options.valueOf("out"); + if (output.isFile()) throw new RuntimeException("Output Path is not a directory"); + if (!output.exists()) output.mkdirs(); + + for (int i = 0; i < input.length; i++) outDir[i] = output; + } else { + for (int i = 0; i < input.length; i++) + outDir[i] = input[i].isDirectory() ? input[i] : input[i].getParentFile(); + } + + for (int i = 0; i < input.length; i++) { + File file = input[i]; + if (file.isDirectory()) { + for (File file2 : file.listFiles()) + if (file2.getName().endsWith(".qb")) convert(file2, outDir[i], flags); + } else convert(file, outDir[i], flags); + } + } + + private void convert(File in, File outDir, int flags) { + System.out.println("Converting: " + in.getName()); + QBImporter.RasterisedModel m = QBImporter.loadQB(in).toRasterisedModel(flags); + m.export(new File(outDir, in.getName().replace(".qb", ".obj")), outDir); + } + + @Override + public String name() { + return "QBConverter"; + } +} diff --git a/src/main/java/codechicken/lib/util/Copyable.java b/src/main/java/codechicken/lib/util/Copyable.java new file mode 100644 index 0000000..ae3de9a --- /dev/null +++ b/src/main/java/codechicken/lib/util/Copyable.java @@ -0,0 +1,6 @@ +package codechicken.lib.util; + +public interface Copyable { + + public T copy(); +} diff --git a/src/main/java/codechicken/lib/util/LangProxy.java b/src/main/java/codechicken/lib/util/LangProxy.java new file mode 100644 index 0000000..0fbebc8 --- /dev/null +++ b/src/main/java/codechicken/lib/util/LangProxy.java @@ -0,0 +1,20 @@ +package codechicken.lib.util; + +import net.minecraft.util.StatCollector; + +public class LangProxy { + + public final String namespace; + + public LangProxy(String namespace) { + this.namespace = namespace + "."; + } + + public String translate(String key) { + return StatCollector.translateToLocal(namespace + key); + } + + public String format(String key, Object... params) { + return StatCollector.translateToLocalFormatted(namespace + key, params); + } +} diff --git a/src/main/java/codechicken/lib/vec/AxisCycle.java b/src/main/java/codechicken/lib/vec/AxisCycle.java new file mode 100644 index 0000000..eafcb70 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/AxisCycle.java @@ -0,0 +1,39 @@ +package codechicken.lib.vec; + +public class AxisCycle { + + public static Transformation[] cycles = new Transformation[] { new RedundantTransformation(), + new VariableTransformation(new Matrix4(0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d0 = vec.x; + double d1 = vec.y; + double d2 = vec.z; + vec.x = d2; + vec.y = d0; + vec.z = d1; + } + + @Override + public Transformation inverse() { + return cycles[2]; + } + }, new VariableTransformation(new Matrix4(0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d0 = vec.x; + double d1 = vec.y; + double d2 = vec.z; + vec.x = d1; + vec.y = d2; + vec.z = d0; + } + + @Override + public Transformation inverse() { + return cycles[1]; + } + } }; +} diff --git a/src/main/java/codechicken/lib/vec/BlockCoord.java b/src/main/java/codechicken/lib/vec/BlockCoord.java new file mode 100644 index 0000000..a631eff --- /dev/null +++ b/src/main/java/codechicken/lib/vec/BlockCoord.java @@ -0,0 +1,206 @@ +package codechicken.lib.vec; + +import net.minecraft.tileentity.TileEntity; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.util.Copyable; + +public class BlockCoord implements Comparable, Copyable { + + public int x; + public int y; + public int z; + + public BlockCoord(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public BlockCoord(Vector3 v) { + this(MathHelper.floor_double(v.x), MathHelper.floor_double(v.y), MathHelper.floor_double(v.z)); + } + + public BlockCoord(TileEntity tile) { + this(tile.xCoord, tile.yCoord, tile.zCoord); + } + + public BlockCoord(int[] ia) { + this(ia[0], ia[1], ia[2]); + } + + public BlockCoord() {} + + public static BlockCoord fromAxes(int[] ia) { + return new BlockCoord(ia[2], ia[0], ia[1]); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BlockCoord)) return false; + BlockCoord o2 = (BlockCoord) obj; + return x == o2.x && y == o2.y && z == o2.z; + } + + @Override + public int hashCode() { + return (x ^ z) * 31 + y; + } + + public int compareTo(BlockCoord o) { + if (x != o.x) return x < o.x ? 1 : -1; + if (y != o.y) return y < o.y ? 1 : -1; + if (z != o.z) return z < o.z ? 1 : -1; + return 0; + } + + public Vector3 toVector3Centered() { + return new Vector3(x + 0.5, y + 0.5, z + 0.5); + } + + public BlockCoord multiply(int i) { + x *= i; + y *= i; + z *= i; + return this; + } + + public double mag() { + return Math.sqrt(x * x + y * y + z * z); + } + + public int mag2() { + return x * x + y * y + z * z; + } + + public boolean isZero() { + return x == 0 && y == 0 && z == 0; + } + + public boolean isAxial() { + return x == 0 ? (y == 0 || z == 0) : (y == 0 && z == 0); + } + + public BlockCoord add(BlockCoord coord2) { + x += coord2.x; + y += coord2.y; + z += coord2.z; + return this; + } + + public BlockCoord add(int i, int j, int k) { + x += i; + y += j; + z += k; + return this; + } + + public BlockCoord sub(BlockCoord coord2) { + x -= coord2.x; + y -= coord2.y; + z -= coord2.z; + return this; + } + + public BlockCoord sub(int i, int j, int k) { + x -= i; + y -= j; + z -= k; + return this; + } + + public BlockCoord offset(int side) { + return offset(side, 1); + } + + public BlockCoord offset(int side, int amount) { + BlockCoord offset = sideOffsets[side]; + x += offset.x * amount; + y += offset.y * amount; + z += offset.z * amount; + return this; + } + + public BlockCoord inset(int side) { + return inset(side, 1); + } + + public BlockCoord inset(int side, int amount) { + return offset(side, -amount); + } + + public int getSide(int side) { + switch (side) { + case 0: + case 1: + return y; + case 2: + case 3: + return z; + case 4: + case 5: + return x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public BlockCoord setSide(int s, int v) { + switch (s) { + case 0, 1 -> y = v; + case 2, 3 -> z = v; + case 4, 5 -> x = v; + default -> throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } + + public static final BlockCoord[] sideOffsets = new BlockCoord[] { new BlockCoord(0, -1, 0), new BlockCoord(0, 1, 0), + new BlockCoord(0, 0, -1), new BlockCoord(0, 0, 1), new BlockCoord(-1, 0, 0), new BlockCoord(1, 0, 0) }; + + public int[] intArray() { + return new int[] { x, y, z }; + } + + public BlockCoord copy() { + return new BlockCoord(x, y, z); + } + + public BlockCoord set(int i, int j, int k) { + x = i; + y = j; + z = k; + return this; + } + + public BlockCoord set(BlockCoord coord) { + return set(coord.x, coord.y, coord.z); + } + + public BlockCoord set(int[] ia) { + return set(ia[0], ia[1], ia[2]); + } + + public BlockCoord set(TileEntity tile) { + return set(tile.xCoord, tile.yCoord, tile.zCoord); + } + + public int toSide() { + if (!isAxial()) return -1; + if (y < 0) return 0; + if (y > 0) return 1; + if (z < 0) return 2; + if (z > 0) return 3; + if (x < 0) return 4; + if (x > 0) return 5; + + return -1; + } + + public int absSum() { + return (x < 0 ? -x : x) + (y < 0 ? -y : y) + (z < 0 ? -z : z); + } + + public String toString() { + return "(" + x + ", " + y + ", " + z + ")"; + } +} diff --git a/src/main/java/codechicken/lib/vec/Cuboid6.java b/src/main/java/codechicken/lib/vec/Cuboid6.java new file mode 100644 index 0000000..2c0e572 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Cuboid6.java @@ -0,0 +1,212 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import net.minecraft.block.Block; +import net.minecraft.util.AxisAlignedBB; + +import codechicken.lib.util.Copyable; + +public class Cuboid6 implements Copyable { + + public static Cuboid6 full = new Cuboid6(0, 0, 0, 1, 1, 1); + + public Vector3 min; + public Vector3 max; + + public Cuboid6(Vector3 min, Vector3 max) { + this.min = min; + this.max = max; + } + + public Cuboid6(AxisAlignedBB aabb) { + min = new Vector3(aabb.minX, aabb.minY, aabb.minZ); + max = new Vector3(aabb.maxX, aabb.maxY, aabb.maxZ); + } + + public Cuboid6(Cuboid6 cuboid) { + min = cuboid.min.copy(); + max = cuboid.max.copy(); + } + + public Cuboid6(double minx, double miny, double minz, double maxx, double maxy, double maxz) { + min = new Vector3(minx, miny, minz); + max = new Vector3(maxx, maxy, maxz); + } + + public AxisAlignedBB toAABB() { + return AxisAlignedBB.getBoundingBox(min.x, min.y, min.z, max.x, max.y, max.z); + } + + public Cuboid6 copy() { + return new Cuboid6(this); + } + + public Cuboid6 set(Cuboid6 c) { + return set(c.min, c.max); + } + + public Cuboid6 set(Vector3 min, Vector3 max) { + this.min.set(min); + this.max.set(max); + return this; + } + + public Cuboid6 set(double minx, double miny, double minz, double maxx, double maxy, double maxz) { + min.set(minx, miny, minz); + max.set(maxx, maxy, maxz); + return this; + } + + public Cuboid6 add(Vector3 vec) { + min.add(vec); + max.add(vec); + return this; + } + + public Cuboid6 sub(Vector3 vec) { + min.subtract(vec); + max.subtract(vec); + return this; + } + + public Cuboid6 expand(double d) { + return expand(new Vector3(d, d, d)); + } + + public Cuboid6 expand(Vector3 vec) { + min.sub(vec); + max.add(vec); + return this; + } + + public void setBlockBounds(Block block) { + block.setBlockBounds((float) min.x, (float) min.y, (float) min.z, (float) max.x, (float) max.y, (float) max.z); + } + + public boolean intersects(Cuboid6 b) { + return max.x - 1E-5 > b.min.x && b.max.x - 1E-5 > min.x + && max.y - 1E-5 > b.min.y + && b.max.y - 1E-5 > min.y + && max.z - 1E-5 > b.min.z + && b.max.z - 1E-5 > min.z; + } + + public Cuboid6 offset(Cuboid6 o) { + min.add(o.min); + max.add(o.max); + return this; + } + + public Vector3 center() { + return min.copy().add(max).multiply(0.5); + } + + public static boolean intersects(Cuboid6 a, Cuboid6 b) { + return a != null && b != null && a.intersects(b); + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Cuboid: (" + new BigDecimal(min.x, cont) + + ", " + + new BigDecimal(min.y, cont) + + ", " + + new BigDecimal(min.z, cont) + + ") -> (" + + new BigDecimal(max.x, cont) + + ", " + + new BigDecimal(max.y, cont) + + ", " + + new BigDecimal(max.z, cont) + + ")"; + } + + public Cuboid6 enclose(Vector3 vec) { + if (min.x > vec.x) min.x = vec.x; + if (min.y > vec.y) min.y = vec.y; + if (min.z > vec.z) min.z = vec.z; + if (max.x < vec.x) max.x = vec.x; + if (max.y < vec.y) max.y = vec.y; + if (max.z < vec.z) max.z = vec.z; + return this; + } + + public Cuboid6 enclose(Cuboid6 c) { + if (min.x > c.min.x) min.x = c.min.x; + if (min.y > c.min.y) min.y = c.min.y; + if (min.z > c.min.z) min.z = c.min.z; + if (max.x < c.max.x) max.x = c.max.x; + if (max.y < c.max.y) max.y = c.max.y; + if (max.z < c.max.z) max.z = c.max.z; + return this; + } + + public Cuboid6 apply(Transformation t) { + t.apply(min); + t.apply(max); + double temp; + if (min.x > max.x) { + temp = min.x; + min.x = max.x; + max.x = temp; + } + if (min.y > max.y) { + temp = min.y; + min.y = max.y; + max.y = temp; + } + if (min.z > max.z) { + temp = min.z; + min.z = max.z; + max.z = temp; + } + return this; + } + + public double getSide(int s) { + switch (s) { + case 0: + return min.y; + case 1: + return max.y; + case 2: + return min.z; + case 3: + return max.z; + case 4: + return min.x; + case 5: + return max.x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public Cuboid6 setSide(int s, double d) { + switch (s) { + case 0: + min.y = d; + break; + case 1: + max.y = d; + break; + case 2: + min.z = d; + break; + case 3: + max.z = d; + break; + case 4: + min.x = d; + break; + case 5: + max.x = d; + break; + default: + throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } +} diff --git a/src/main/java/codechicken/lib/vec/CuboidCoord.java b/src/main/java/codechicken/lib/vec/CuboidCoord.java new file mode 100644 index 0000000..29448fc --- /dev/null +++ b/src/main/java/codechicken/lib/vec/CuboidCoord.java @@ -0,0 +1,233 @@ +package codechicken.lib.vec; + +import java.util.Iterator; + +import net.minecraft.util.AxisAlignedBB; + +import codechicken.lib.util.Copyable; + +public class CuboidCoord implements Iterable, Copyable { + + public BlockCoord min; + public BlockCoord max; + + public CuboidCoord() { + min = new BlockCoord(); + max = new BlockCoord(); + } + + public CuboidCoord(BlockCoord min, BlockCoord max) { + this.min = min; + this.max = max; + } + + public CuboidCoord(BlockCoord coord) { + this(coord, coord.copy()); + } + + public CuboidCoord(int[] ia) { + this(ia[0], ia[1], ia[2], ia[3], ia[4], ia[5]); + } + + public CuboidCoord(int x1, int y1, int z1, int x2, int y2, int z2) { + this(new BlockCoord(x1, y1, z1), new BlockCoord(x2, y2, z2)); + } + + public CuboidCoord expand(int amount) { + return expand(amount, amount, amount); + } + + public CuboidCoord expand(int x, int y, int z) { + max.add(x, y, z); + min.sub(x, y, z); + return this; + } + + public CuboidCoord expand(int side, int amount) { + if (side % 2 == 0) // negative side + min = min.offset(side, amount); + else max = max.offset(side, amount); + return this; + } + + public CuboidCoord offset(BlockCoord b) { + min.add(b); + max.add(b); + return this; + } + + public CuboidCoord offset(int x, int y, int z) { + min.add(x, y, z); + max.add(x, y, z); + return this; + } + + public int size(int s) { + switch (s) { + case 0: + case 1: + return max.y - min.y + 1; + case 2: + case 3: + return max.z - min.z + 1; + case 4: + case 5: + return max.x - min.x + 1; + default: + return 0; + } + } + + public int getSide(int s) { + switch (s) { + case 0: + return min.y; + case 1: + return max.y; + case 2: + return min.z; + case 3: + return max.z; + case 4: + return min.x; + case 5: + return max.x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public CuboidCoord setSide(int s, int v) { + switch (s) { + case 0: + min.y = v; + break; + case 1: + max.y = v; + break; + case 2: + min.z = v; + break; + case 3: + max.z = v; + break; + case 4: + min.x = v; + break; + case 5: + max.x = v; + break; + default: + throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } + + public int getVolume() { + return (max.x - min.x + 1) * (max.y - min.y + 1) * (max.z - min.z + 1); + } + + public Vector3 getCenterVec() { + return new Vector3( + min.x + (max.x - min.x + 1) / 2D, + min.y + (max.y - min.y + 1) / 2D, + min.z + (max.z - min.z + 1) / 2D); + } + + public BlockCoord getCenter(BlockCoord store) { + store.set(min.x + (max.x - min.x) / 2, min.y + (max.y - min.y) / 2, min.z + (max.z - min.z) / 2); + return store; + } + + public boolean contains(BlockCoord coord) { + return contains(coord.x, coord.y, coord.z); + } + + public boolean contains(int x, int y, int z) { + return x >= min.x && x <= max.x && y >= min.y && y <= max.y && z >= min.z && z <= max.z; + } + + public int[] intArray() { + return new int[] { min.x, min.y, min.z, max.x, max.y, max.z }; + } + + public CuboidCoord copy() { + return new CuboidCoord(min.copy(), max.copy()); + } + + public Cuboid6 bounds() { + return new Cuboid6(min.x, min.y, min.z, max.x + 1, max.y + 1, max.z + 1); + } + + public AxisAlignedBB toAABB() { + return bounds().toAABB(); + } + + public CuboidCoord set(CuboidCoord c) { + return set(c.min, c.max); + } + + public CuboidCoord set(BlockCoord min, BlockCoord max) { + this.min.set(min); + this.max.set(max); + return this; + } + + public CuboidCoord set(int x1, int y1, int z1, int x2, int y2, int z2) { + min.set(x1, y1, z1); + max.set(x2, y2, z2); + return this; + } + + public CuboidCoord set(BlockCoord coord) { + return set(coord, coord); + } + + public CuboidCoord set(int[] ia) { + return set(ia[0], ia[1], ia[2], ia[3], ia[4], ia[5]); + } + + public CuboidCoord include(BlockCoord coord) { + return include(coord.x, coord.y, coord.z); + } + + public CuboidCoord include(int x, int y, int z) { + if (x < min.x) min.x = x; + else if (x > max.x) max.x = x; + if (y < min.y) min.y = y; + else if (y > max.y) max.y = y; + if (z < min.z) min.z = z; + else if (z > max.z) max.z = z; + return this; + } + + public Iterator iterator() { + return new Iterator() { + + BlockCoord b = null; + + public boolean hasNext() { + return b == null || !b.equals(max); + } + + public BlockCoord next() { + if (b == null) b = min.copy(); + else { + if (b.z != max.z) b.z++; + else { + b.z = min.z; + if (b.y != max.y) b.y++; + else { + b.y = min.y; + b.x++; + } + } + } + return b.copy(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/src/main/java/codechicken/lib/vec/ITransformation.java b/src/main/java/codechicken/lib/vec/ITransformation.java new file mode 100644 index 0000000..8df95a9 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/ITransformation.java @@ -0,0 +1,52 @@ +package codechicken.lib.vec; + +/** + * Abstract supertype for any VectorN transformation + * + * @param The vector type + * @param The transformation type + */ +public abstract class ITransformation { + + /** + * Applies this transformation to vec + */ + public abstract void apply(Vector vec); + + /** + * @param point The point to apply this transformation around + * @return Wraps this transformation in a translation to point and then back from point + */ + public abstract Transformation at(Vector point); + + /** + * Creates a TransformationList composed of this transformation followed by t If this is a TransformationList, the + * transformation will be appended and this returned + */ + public abstract Transformation with(Transformation t); + + /** + * Returns a simplified transformation that performs this, followed by next. If such a transformation does not + * exist, returns null + */ + public Transformation merge(Transformation next) { + return null; + } + + /** + * Returns true if this transformation is redundant, eg. Scale(1, 1, 1), Translation(0, 0, 0) or Rotation(0, a, b, + * c) + */ + public boolean isRedundant() { + return false; + } + + public abstract Transformation inverse(); + + /** + * Scala ++ operator + */ + public Transformation $plus$plus(Transformation t) { + return with(t); + } +} diff --git a/src/main/java/codechicken/lib/vec/IrreversibleTransformationException.java b/src/main/java/codechicken/lib/vec/IrreversibleTransformationException.java new file mode 100644 index 0000000..ec71e40 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/IrreversibleTransformationException.java @@ -0,0 +1,16 @@ +package codechicken.lib.vec; + +@SuppressWarnings("serial") +public class IrreversibleTransformationException extends RuntimeException { + + public ITransformation t; + + public IrreversibleTransformationException(ITransformation t) { + this.t = t; + } + + @Override + public String getMessage() { + return "The following transformation is irreversible:\n" + t; + } +} diff --git a/src/main/java/codechicken/lib/vec/Line3.java b/src/main/java/codechicken/lib/vec/Line3.java new file mode 100644 index 0000000..6b9ae92 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Line3.java @@ -0,0 +1,46 @@ +package codechicken.lib.vec; + +public class Line3 { + + public static final double tol = 0.0001D; + + public Vector3 pt1; + public Vector3 pt2; + + public Line3(Vector3 pt1, Vector3 pt2) { + this.pt1 = pt1; + this.pt2 = pt2; + } + + public Line3() { + this(new Vector3(), new Vector3()); + } + + public static boolean intersection2D(Line3 line1, Line3 line2, Vector3 store) { + // calculate differences + double xD1 = line1.pt2.x - line1.pt1.x; + double zD1 = line1.pt2.z - line1.pt1.z; + double xD2 = line2.pt2.x - line2.pt1.x; + double zD2 = line2.pt2.z - line2.pt1.z; + + double xD3 = line1.pt1.x - line2.pt1.x; + double zD3 = line1.pt1.z - line2.pt1.z; + + double div = zD2 * xD1 - xD2 * zD1; + if (div == 0) // lines are parallel + return false; + double ua = (xD2 * zD3 - zD2 * xD3) / div; + store.set(line1.pt1.x + ua * xD1, 0, line1.pt1.z + ua * zD1); + + if (store.x >= Math.min(line1.pt1.x, line1.pt2.x) - tol && store.x >= Math.min(line2.pt1.x, line2.pt2.x) - tol + && store.z >= Math.min(line1.pt1.z, line1.pt2.z) - tol + && store.z >= Math.min(line2.pt1.z, line2.pt2.z) - tol + && store.x <= Math.max(line1.pt1.x, line1.pt2.x) + tol + && store.x <= Math.max(line2.pt1.x, line2.pt2.x) + tol + && store.z <= Math.max(line1.pt1.z, line1.pt2.z) + tol + && store.z <= Math.max(line2.pt1.z, line2.pt2.z) + tol) + return true; + + return false; + } +} diff --git a/src/main/java/codechicken/lib/vec/Matrix4.java b/src/main/java/codechicken/lib/vec/Matrix4.java new file mode 100644 index 0000000..47e5ea8 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Matrix4.java @@ -0,0 +1,389 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.DoubleBuffer; + +import org.lwjgl.opengl.GL11; + +import codechicken.lib.util.Copyable; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Matrix4 extends Transformation implements Copyable { + + private static DoubleBuffer glBuf = ByteBuffer.allocateDirect(16 * 8).order(ByteOrder.nativeOrder()) + .asDoubleBuffer(); + + // m + public double m00, m01, m02, m03; + public double m10, m11, m12, m13; + public double m20, m21, m22, m23; + public double m30, m31, m32, m33; + + public Matrix4() { + m00 = m11 = m22 = m33 = 1; + } + + public Matrix4(double d00, double d01, double d02, double d03, double d10, double d11, double d12, double d13, + double d20, double d21, double d22, double d23, double d30, double d31, double d32, double d33) { + m00 = d00; + m01 = d01; + m02 = d02; + m03 = d03; + m10 = d10; + m11 = d11; + m12 = d12; + m13 = d13; + m20 = d20; + m21 = d21; + m22 = d22; + m23 = d23; + m30 = d30; + m31 = d31; + m32 = d32; + m33 = d33; + } + + public Matrix4(Matrix4 mat) { + set(mat); + } + + public Matrix4 setIdentity() { + m00 = m11 = m22 = m33 = 1; + m01 = m02 = m03 = m10 = m12 = m13 = m20 = m21 = m23 = m30 = m31 = m32 = 0; + + return this; + } + + public Matrix4 translate(Vector3 vec) { + m03 += m00 * vec.x + m01 * vec.y + m02 * vec.z; + m13 += m10 * vec.x + m11 * vec.y + m12 * vec.z; + m23 += m20 * vec.x + m21 * vec.y + m22 * vec.z; + m33 += m30 * vec.x + m31 * vec.y + m32 * vec.z; + + return this; + } + + public Matrix4 translate(double x, double y, double z) { + m03 += m00 * x + m01 * y + m02 * z; + m13 += m10 * x + m11 * y + m12 * z; + m23 += m20 * x + m21 * y + m22 * z; + m33 += m30 * x + m31 * y + m32 * z; + + return this; + } + + public Matrix4 scale(Vector3 vec) { + m00 *= vec.x; + m10 *= vec.x; + m20 *= vec.x; + m30 *= vec.x; + m01 *= vec.y; + m11 *= vec.y; + m21 *= vec.y; + m31 *= vec.y; + m02 *= vec.z; + m12 *= vec.z; + m22 *= vec.z; + m32 *= vec.z; + + return this; + } + + public Matrix4 scale(double scale) { + m00 *= scale; + m10 *= scale; + m20 *= scale; + m30 *= scale; + m01 *= scale; + m11 *= scale; + m21 *= scale; + m31 *= scale; + m02 *= scale; + m12 *= scale; + m22 *= scale; + m32 *= scale; + return this; + } + + public Matrix4 rotate(double angle, Vector3 axis) { + double c = Math.cos(angle); + double s = Math.sin(angle); + double mc = 1.0f - c; + double xy = axis.x * axis.y; + double yz = axis.y * axis.z; + double xz = axis.x * axis.z; + double xs = axis.x * s; + double ys = axis.y * s; + double zs = axis.z * s; + + double f00 = axis.x * axis.x * mc + c; + double f10 = xy * mc + zs; + double f20 = xz * mc - ys; + + double f01 = xy * mc - zs; + double f11 = axis.y * axis.y * mc + c; + double f21 = yz * mc + xs; + + double f02 = xz * mc + ys; + double f12 = yz * mc - xs; + double f22 = axis.z * axis.z * mc + c; + + double t00 = m00 * f00 + m01 * f10 + m02 * f20; + double t10 = m10 * f00 + m11 * f10 + m12 * f20; + double t20 = m20 * f00 + m21 * f10 + m22 * f20; + double t30 = m30 * f00 + m31 * f10 + m32 * f20; + double t01 = m00 * f01 + m01 * f11 + m02 * f21; + double t11 = m10 * f01 + m11 * f11 + m12 * f21; + double t21 = m20 * f01 + m21 * f11 + m22 * f21; + double t31 = m30 * f01 + m31 * f11 + m32 * f21; + m02 = m00 * f02 + m01 * f12 + m02 * f22; + m12 = m10 * f02 + m11 * f12 + m12 * f22; + m22 = m20 * f02 + m21 * f12 + m22 * f22; + m32 = m30 * f02 + m31 * f12 + m32 * f22; + m00 = t00; + m10 = t10; + m20 = t20; + m30 = t30; + m01 = t01; + m11 = t11; + m21 = t21; + m31 = t31; + + return this; + } + + public Matrix4 rotate(Rotation rotation) { + rotation.apply(this); + return this; + } + + public Matrix4 leftMultiply(Matrix4 mat) { + double n00 = m00 * mat.m00 + m10 * mat.m01 + m20 * mat.m02 + m30 * mat.m03; + double n01 = m01 * mat.m00 + m11 * mat.m01 + m21 * mat.m02 + m31 * mat.m03; + double n02 = m02 * mat.m00 + m12 * mat.m01 + m22 * mat.m02 + m32 * mat.m03; + double n03 = m03 * mat.m00 + m13 * mat.m01 + m23 * mat.m02 + m33 * mat.m03; + double n10 = m00 * mat.m10 + m10 * mat.m11 + m20 * mat.m12 + m30 * mat.m13; + double n11 = m01 * mat.m10 + m11 * mat.m11 + m21 * mat.m12 + m31 * mat.m13; + double n12 = m02 * mat.m10 + m12 * mat.m11 + m22 * mat.m12 + m32 * mat.m13; + double n13 = m03 * mat.m10 + m13 * mat.m11 + m23 * mat.m12 + m33 * mat.m13; + double n20 = m00 * mat.m20 + m10 * mat.m21 + m20 * mat.m22 + m30 * mat.m23; + double n21 = m01 * mat.m20 + m11 * mat.m21 + m21 * mat.m22 + m31 * mat.m23; + double n22 = m02 * mat.m20 + m12 * mat.m21 + m22 * mat.m22 + m32 * mat.m23; + double n23 = m03 * mat.m20 + m13 * mat.m21 + m23 * mat.m22 + m33 * mat.m23; + double n30 = m00 * mat.m30 + m10 * mat.m31 + m20 * mat.m32 + m30 * mat.m33; + double n31 = m01 * mat.m30 + m11 * mat.m31 + m21 * mat.m32 + m31 * mat.m33; + double n32 = m02 * mat.m30 + m12 * mat.m31 + m22 * mat.m32 + m32 * mat.m33; + double n33 = m03 * mat.m30 + m13 * mat.m31 + m23 * mat.m32 + m33 * mat.m33; + + m00 = n00; + m01 = n01; + m02 = n02; + m03 = n03; + m10 = n10; + m11 = n11; + m12 = n12; + m13 = n13; + m20 = n20; + m21 = n21; + m22 = n22; + m23 = n23; + m30 = n30; + m31 = n31; + m32 = n32; + m33 = n33; + + return this; + } + + public Matrix4 multiply(Matrix4 mat) { + double n00 = m00 * mat.m00 + m01 * mat.m10 + m02 * mat.m20 + m03 * mat.m30; + double n01 = m00 * mat.m01 + m01 * mat.m11 + m02 * mat.m21 + m03 * mat.m31; + double n02 = m00 * mat.m02 + m01 * mat.m12 + m02 * mat.m22 + m03 * mat.m32; + double n03 = m00 * mat.m03 + m01 * mat.m13 + m02 * mat.m23 + m03 * mat.m33; + double n10 = m10 * mat.m00 + m11 * mat.m10 + m12 * mat.m20 + m13 * mat.m30; + double n11 = m10 * mat.m01 + m11 * mat.m11 + m12 * mat.m21 + m13 * mat.m31; + double n12 = m10 * mat.m02 + m11 * mat.m12 + m12 * mat.m22 + m13 * mat.m32; + double n13 = m10 * mat.m03 + m11 * mat.m13 + m12 * mat.m23 + m13 * mat.m33; + double n20 = m20 * mat.m00 + m21 * mat.m10 + m22 * mat.m20 + m23 * mat.m30; + double n21 = m20 * mat.m01 + m21 * mat.m11 + m22 * mat.m21 + m23 * mat.m31; + double n22 = m20 * mat.m02 + m21 * mat.m12 + m22 * mat.m22 + m23 * mat.m32; + double n23 = m20 * mat.m03 + m21 * mat.m13 + m22 * mat.m23 + m23 * mat.m33; + double n30 = m30 * mat.m00 + m31 * mat.m10 + m32 * mat.m20 + m33 * mat.m30; + double n31 = m30 * mat.m01 + m31 * mat.m11 + m32 * mat.m21 + m33 * mat.m31; + double n32 = m30 * mat.m02 + m31 * mat.m12 + m32 * mat.m22 + m33 * mat.m32; + double n33 = m30 * mat.m03 + m31 * mat.m13 + m32 * mat.m23 + m33 * mat.m33; + + m00 = n00; + m01 = n01; + m02 = n02; + m03 = n03; + m10 = n10; + m11 = n11; + m12 = n12; + m13 = n13; + m20 = n20; + m21 = n21; + m22 = n22; + m23 = n23; + m30 = n30; + m31 = n31; + m32 = n32; + m33 = n33; + + return this; + } + + public Matrix4 transpose() { + double n00 = m00; + double n10 = m01; + double n20 = m02; + double n30 = m03; + double n01 = m10; + double n11 = m11; + double n21 = m12; + double n31 = m13; + double n02 = m20; + double n12 = m21; + double n22 = m22; + double n32 = m23; + double n03 = m30; + double n13 = m31; + double n23 = m32; + double n33 = m33; + + m00 = n00; + m01 = n01; + m02 = n02; + m03 = n03; + m10 = n10; + m11 = n11; + m12 = n12; + m13 = n13; + m20 = n20; + m21 = n21; + m22 = n22; + m23 = n23; + m30 = n30; + m31 = n31; + m32 = n32; + m33 = n33; + + return this; + } + + public Matrix4 copy() { + return new Matrix4(this); + } + + public Matrix4 set(Matrix4 mat) { + m00 = mat.m00; + m01 = mat.m01; + m02 = mat.m02; + m03 = mat.m03; + m10 = mat.m10; + m11 = mat.m11; + m12 = mat.m12; + m13 = mat.m13; + m20 = mat.m20; + m21 = mat.m21; + m22 = mat.m22; + m23 = mat.m23; + m30 = mat.m30; + m31 = mat.m31; + m32 = mat.m32; + m33 = mat.m33; + + return this; + } + + @Override + public void apply(Matrix4 mat) { + mat.multiply(this); + } + + private void mult3x3(Vector3 vec) { + double x = m00 * vec.x + m01 * vec.y + m02 * vec.z; + double y = m10 * vec.x + m11 * vec.y + m12 * vec.z; + double z = m20 * vec.x + m21 * vec.y + m22 * vec.z; + + vec.x = x; + vec.y = y; + vec.z = z; + } + + @Override + public void apply(Vector3 vec) { + mult3x3(vec); + vec.add(m03, m13, m23); + } + + @Override + public void applyN(Vector3 vec) { + mult3x3(vec); + vec.normalize(); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "[" + new BigDecimal(m00, cont) + + "," + + new BigDecimal(m01, cont) + + "," + + new BigDecimal(m02, cont) + + "," + + new BigDecimal(m03, cont) + + "]\n" + + "[" + + new BigDecimal(m10, cont) + + "," + + new BigDecimal(m11, cont) + + "," + + new BigDecimal(m12, cont) + + "," + + new BigDecimal(m13, cont) + + "]\n" + + "[" + + new BigDecimal(m20, cont) + + "," + + new BigDecimal(m21, cont) + + "," + + new BigDecimal(m22, cont) + + "," + + new BigDecimal(m23, cont) + + "]\n" + + "[" + + new BigDecimal(m30, cont) + + "," + + new BigDecimal(m31, cont) + + "," + + new BigDecimal(m32, cont) + + "," + + new BigDecimal(m33, cont) + + "]"; + } + + public Matrix4 apply(Transformation t) { + t.apply(this); + return this; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + glBuf.put(m00).put(m10).put(m20).put(m30).put(m01).put(m11).put(m21).put(m31).put(m02).put(m12).put(m22) + .put(m32).put(m03).put(m13).put(m23).put(m33); + glBuf.flip(); + GL11.glMultMatrix(glBuf); + } + + @Override + public Transformation inverse() { + throw new IrreversibleTransformationException(this); // Don't waste your cpu with matrix inverses + } +} diff --git a/src/main/java/codechicken/lib/vec/Quat.java b/src/main/java/codechicken/lib/vec/Quat.java new file mode 100644 index 0000000..da2ac5d --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Quat.java @@ -0,0 +1,146 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.util.Copyable; + +public class Quat implements Copyable { + + public double x; + public double y; + public double z; + public double s; + + public Quat() { + s = 1; + x = 0; + y = 0; + z = 0; + } + + public Quat(Quat quat) { + x = quat.x; + y = quat.y; + z = quat.z; + s = quat.s; + } + + public Quat(double d, double d1, double d2, double d3) { + x = d1; + y = d2; + z = d3; + s = d; + } + + public Quat set(Quat quat) { + x = quat.x; + y = quat.y; + z = quat.z; + s = quat.s; + + return this; + } + + public Quat set(double d, double d1, double d2, double d3) { + x = d1; + y = d2; + z = d3; + s = d; + + return this; + } + + public static Quat aroundAxis(double ax, double ay, double az, double angle) { + return new Quat().setAroundAxis(ax, ay, az, angle); + } + + public static Quat aroundAxis(Vector3 axis, double angle) { + return aroundAxis(axis.x, axis.y, axis.z, angle); + } + + public Quat setAroundAxis(double ax, double ay, double az, double angle) { + angle *= 0.5; + double d4 = MathHelper.sin(angle); + return set(MathHelper.cos(angle), ax * d4, ay * d4, az * d4); + } + + public Quat setAroundAxis(Vector3 axis, double angle) { + return setAroundAxis(axis.x, axis.y, axis.z, angle); + } + + public Quat multiply(Quat quat) { + double d = s * quat.s - x * quat.x - y * quat.y - z * quat.z; + double d1 = s * quat.x + x * quat.s - y * quat.z + z * quat.y; + double d2 = s * quat.y + x * quat.z + y * quat.s - z * quat.x; + double d3 = s * quat.z - x * quat.y + y * quat.x + z * quat.s; + s = d; + x = d1; + y = d2; + z = d3; + + return this; + } + + public Quat rightMultiply(Quat quat) { + double d = s * quat.s - x * quat.x - y * quat.y - z * quat.z; + double d1 = s * quat.x + x * quat.s + y * quat.z - z * quat.y; + double d2 = s * quat.y - x * quat.z + y * quat.s + z * quat.x; + double d3 = s * quat.z + x * quat.y - y * quat.x + z * quat.s; + s = d; + x = d1; + y = d2; + z = d3; + + return this; + } + + public double mag() { + return Math.sqrt(x * x + y * y + z * z + s * s); + } + + public Quat normalize() { + double d = mag(); + if (d != 0) { + d = 1 / d; + x *= d; + y *= d; + z *= d; + s *= d; + } + + return this; + } + + public Quat copy() { + return new Quat(this); + } + + public void rotate(Vector3 vec) { + double d = -x * vec.x - y * vec.y - z * vec.z; + double d1 = s * vec.x + y * vec.z - z * vec.y; + double d2 = s * vec.y - x * vec.z + z * vec.x; + double d3 = s * vec.z + x * vec.y - y * vec.x; + vec.x = d1 * s - d * x - d2 * z + d3 * y; + vec.y = d2 * s - d * y + d1 * z - d3 * x; + vec.z = d3 * s - d * z - d1 * y + d2 * x; + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Quat(" + new BigDecimal(s, cont) + + ", " + + new BigDecimal(x, cont) + + ", " + + new BigDecimal(y, cont) + + ", " + + new BigDecimal(z, cont) + + ")"; + } + + public Rotation rotation() { + return new Rotation(this); + } +} diff --git a/src/main/java/codechicken/lib/vec/Rectangle4i.java b/src/main/java/codechicken/lib/vec/Rectangle4i.java new file mode 100644 index 0000000..9dd6e81 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Rectangle4i.java @@ -0,0 +1,91 @@ +package codechicken.lib.vec; + +public class Rectangle4i { + + public int x; + public int y; + public int w; + public int h; + + public Rectangle4i() {} + + public Rectangle4i(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + public int x1() { + return x; + } + + public int y1() { + return y; + } + + public int x2() { + return x + w - 1; + } + + public int y2() { + return y + h - 1; + } + + public void set(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + public Rectangle4i offset(int dx, int dy) { + x += dx; + y += dy; + return this; + } + + @Deprecated + public Rectangle4i with(int px, int py) { + return include(px, py); + } + + public Rectangle4i include(int px, int py) { + if (px < x) expand(px - x, 0); + if (px >= x + w) expand(px - x - w + 1, 0); + if (py < y) expand(0, py - y); + if (py >= y + h) expand(0, py - y - h + 1); + return this; + } + + public Rectangle4i include(Rectangle4i r) { + include(r.x, r.y); + return include(r.x2(), r.y2()); + } + + public Rectangle4i expand(int px, int py) { + if (px > 0) w += px; + else { + x += px; + w -= px; + } + if (py > 0) h += py; + else { + y += py; + h -= py; + } + return this; + } + + public boolean contains(int px, int py) { + return x <= px && px < x + w && y <= py && py < y + h; + } + + public boolean intersects(Rectangle4i r) { + return r.x + r.w > x && r.x < x + w && r.y + r.h > y && r.y < y + h; + } + + public int area() { + return w * h; + } +} diff --git a/src/main/java/codechicken/lib/vec/RedundantTransformation.java b/src/main/java/codechicken/lib/vec/RedundantTransformation.java new file mode 100644 index 0000000..d58da7f --- /dev/null +++ b/src/main/java/codechicken/lib/vec/RedundantTransformation.java @@ -0,0 +1,45 @@ +package codechicken.lib.vec; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class RedundantTransformation extends Transformation { + + @Override + public void apply(Vector3 vec) {} + + @Override + public void apply(Matrix4 mat) {} + + @Override + public void applyN(Vector3 normal) {} + + @Override + public Transformation at(Vector3 point) { + return this; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() {} + + @Override + public Transformation inverse() { + return this; + } + + @Override + public Transformation merge(Transformation next) { + return next; + } + + @Override + public boolean isRedundant() { + return true; + } + + @Override + public String toString() { + return "Nothing()"; + } +} diff --git a/src/main/java/codechicken/lib/vec/Rotation.java b/src/main/java/codechicken/lib/vec/Rotation.java new file mode 100644 index 0000000..15c50e2 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Rotation.java @@ -0,0 +1,295 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; + +import org.lwjgl.opengl.GL11; + +import codechicken.lib.math.MathHelper; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Rotation extends Transformation { + + /** + * Clockwise pi/2 about y looking down + */ + public static Transformation[] quarterRotations = new Transformation[] { new RedundantTransformation(), + new VariableTransformation(new Matrix4(0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d1 = vec.x; + double d2 = vec.z; + vec.x = -d2; + vec.z = d1; + } + + @Override + public Transformation inverse() { + return quarterRotations[3]; + } + }, new VariableTransformation(new Matrix4(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + vec.x = -vec.x; + vec.z = -vec.z; + } + + @Override + public Transformation inverse() { + return this; + } + }, new VariableTransformation(new Matrix4(0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d1 = vec.x; + double d2 = vec.z; + vec.x = d2; + vec.z = -d1; + } + + @Override + public Transformation inverse() { + return quarterRotations[1]; + } + } }; + + public static Transformation[] sideRotations = new Transformation[] { new RedundantTransformation(), + new VariableTransformation(new Matrix4(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + vec.y = -vec.y; + vec.z = -vec.z; + } + + @Override + public Transformation inverse() { + return this; + } + }, new VariableTransformation(new Matrix4(1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d1 = vec.y; + double d2 = vec.z; + vec.y = -d2; + vec.z = d1; + } + + @Override + public Transformation inverse() { + return sideRotations[3]; + } + }, new VariableTransformation(new Matrix4(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d1 = vec.y; + double d2 = vec.z; + vec.y = d2; + vec.z = -d1; + } + + @Override + public Transformation inverse() { + return sideRotations[2]; + } + }, new VariableTransformation(new Matrix4(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d0 = vec.x; + double d1 = vec.y; + vec.x = d1; + vec.y = -d0; + } + + @Override + public Transformation inverse() { + return sideRotations[5]; + } + }, new VariableTransformation(new Matrix4(0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d0 = vec.x; + double d1 = vec.y; + vec.x = -d1; + vec.y = d0; + } + + @Override + public Transformation inverse() { + return sideRotations[4]; + } + } }; + + public static Vector3[] axes = new Vector3[] { new Vector3(0, -1, 0), new Vector3(0, 1, 0), new Vector3(0, 0, -1), + new Vector3(0, 0, 1), new Vector3(-1, 0, 0), new Vector3(1, 0, 0) }; + + public static int[] sideRotMap = new int[] { 3, 4, 2, 5, 3, 5, 2, 4, 1, 5, 0, 4, 1, 4, 0, 5, 1, 2, 0, 3, 1, 3, 0, + 2 }; + + public static int[] rotSideMap = new int[] { -1, -1, 2, 0, 1, 3, -1, -1, 2, 0, 3, 1, 2, 0, -1, -1, 3, 1, 2, 0, -1, + -1, 1, 3, 2, 0, 1, 3, -1, -1, 2, 0, 3, 1, -1, -1 }; + + /** + * Rotate pi/2 * this offset for [side] about y axis before rotating to the side for the rotation indicies to line + * up + */ + public static int[] sideRotOffsets = new int[] { 0, 2, 2, 0, 1, 3 }; + + public static int rotateSide(int s, int r) { + return sideRotMap[s << 2 | r]; + } + + /** + * Reverse of rotateSide + */ + public static int rotationTo(int s1, int s2) { + if ((s1 & 6) == (s2 & 6)) throw new IllegalArgumentException("Faces " + s1 + " and " + s2 + " are opposites"); + return rotSideMap[s1 * 6 + s2]; + } + + /** + * @param player The placing player, used for obtaining the look vector + * @param side The side of the block being placed on + * @return The rotation for the face == side^1 + */ + public static int getSidedRotation(EntityPlayer player, int side) { + Vector3 look = new Vector3(player.getLook(1)); + double max = 0; + int maxr = 0; + for (int r = 0; r < 4; r++) { + Vector3 axis = Rotation.axes[rotateSide(side ^ 1, r)]; + double d = look.scalarProject(axis); + if (d > max) { + max = d; + maxr = r; + } + } + return maxr; + } + + /** + * @return The rotation quat for side 0 and rotation 0 to side s with rotation r + */ + public static Transformation sideOrientation(int s, int r) { + return quarterRotations[(r + sideRotOffsets[s]) % 4].with(sideRotations[s]); + } + + /** + * @param entity The placing entity, used for obtaining the look vector + * @return The side towards which the entity is most directly looking. + */ + public static int getSideFromLookAngle(EntityLivingBase entity) { + Vector3 look = new Vector3(entity.getLook(1)); + double max = 0; + int maxs = 0; + for (int s = 0; s < 6; s++) { + double d = look.scalarProject(axes[s]); + if (d > max) { + max = d; + maxs = s; + } + } + return maxs; + } + + public double angle; + public Vector3 axis; + + private Quat quat; + + public Rotation(double angle, Vector3 axis) { + this.angle = angle; + this.axis = axis; + } + + public Rotation(double angle, double x, double y, double z) { + this(angle, new Vector3(x, y, z)); + } + + public Rotation(Quat quat) { + this.quat = quat; + + angle = Math.acos(quat.s) * 2; + if (angle == 0) { + axis = new Vector3(0, 1, 0); + } else { + double sa = Math.sin(angle * 0.5); + axis = new Vector3(quat.x / sa, quat.y / sa, quat.z / sa); + } + } + + @Override + public void apply(Vector3 vec) { + if (quat == null) quat = Quat.aroundAxis(axis, angle); + + vec.rotate(quat); + } + + @Override + public void applyN(Vector3 normal) { + apply(normal); + } + + @Override + public void apply(Matrix4 mat) { + mat.rotate(angle, axis); + } + + public Quat toQuat() { + if (quat == null) quat = Quat.aroundAxis(axis, angle); + return quat; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + GL11.glRotatef((float) (angle * MathHelper.todeg), (float) axis.x, (float) axis.y, (float) axis.z); + } + + @Override + public Transformation inverse() { + return new Rotation(-angle, axis); + } + + @Override + public Transformation merge(Transformation next) { + if (next instanceof Rotation) { + Rotation r = (Rotation) next; + if (r.axis.equalsT(axis)) return new Rotation(angle + r.angle, axis); + + return new Rotation(toQuat().copy().multiply(r.toQuat())); + } + + return null; + } + + @Override + public boolean isRedundant() { + return MathHelper.between(-1E-5, angle, 1E-5); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Rotation(" + new BigDecimal(angle, cont) + + ", " + + new BigDecimal(axis.x, cont) + + ", " + + new BigDecimal(axis.y, cont) + + ", " + + new BigDecimal(axis.z, cont) + + ")"; + } +} diff --git a/src/main/java/codechicken/lib/vec/Scale.java b/src/main/java/codechicken/lib/vec/Scale.java new file mode 100644 index 0000000..19b2e8c --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Scale.java @@ -0,0 +1,74 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import org.lwjgl.opengl.GL11; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Scale extends Transformation { + + public Vector3 factor; + + public Scale(Vector3 factor) { + this.factor = factor; + } + + public Scale(double factor) { + this(new Vector3(factor, factor, factor)); + } + + public Scale(double x, double y, double z) { + this(new Vector3(x, y, z)); + } + + @Override + public void apply(Vector3 vec) { + vec.multiply(factor); + } + + @Override + public void applyN(Vector3 normal) {} + + @Override + public void apply(Matrix4 mat) { + mat.scale(factor); + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + GL11.glScaled(factor.x, factor.y, factor.z); + } + + @Override + public Transformation inverse() { + return new Scale(1 / factor.x, 1 / factor.y, 1 / factor.z); + } + + @Override + public Transformation merge(Transformation next) { + if (next instanceof Scale) return new Scale(factor.copy().multiply(((Scale) next).factor)); + + return null; + } + + @Override + public boolean isRedundant() { + return factor.equalsT(Vector3.one); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Scale(" + new BigDecimal(factor.x, cont) + + ", " + + new BigDecimal(factor.y, cont) + + ", " + + new BigDecimal(factor.z, cont) + + ")"; + } +} diff --git a/src/main/java/codechicken/lib/vec/SwapYZ.java b/src/main/java/codechicken/lib/vec/SwapYZ.java new file mode 100644 index 0000000..ad256dd --- /dev/null +++ b/src/main/java/codechicken/lib/vec/SwapYZ.java @@ -0,0 +1,20 @@ +package codechicken.lib.vec; + +public class SwapYZ extends VariableTransformation { + + public SwapYZ() { + super(new Matrix4(1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1)); + } + + @Override + public void apply(Vector3 vec) { + double vz = vec.z; + vec.z = vec.y; + vec.y = vz; + } + + @Override + public Transformation inverse() { + return this; + } +} diff --git a/src/main/java/codechicken/lib/vec/Transformation.java b/src/main/java/codechicken/lib/vec/Transformation.java new file mode 100644 index 0000000..56b63c3 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Transformation.java @@ -0,0 +1,56 @@ +package codechicken.lib.vec; + +import codechicken.lib.render.CCRenderState; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * Abstract supertype for any 3D vector transformation + */ +public abstract class Transformation extends ITransformation + implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + + /** + * Applies this transformation to a normal (doesn't translate) + * + * @param normal The normal to transform + */ + public abstract void applyN(Vector3 normal); + + /** + * Applies this transformation to a matrix as a multiplication on the right hand side. + * + * @param mat The matrix to combine this transformation with + */ + public abstract void apply(Matrix4 mat); + + public Transformation at(Vector3 point) { + return new TransformationList(new Translation(-point.x, -point.y, -point.z), this, point.translation()); + } + + public TransformationList with(Transformation t) { + return new TransformationList(this, t); + } + + @SideOnly(Side.CLIENT) + public abstract void glApply(); + + @Override + public boolean load(CCRenderState state) { + state.pipeline.addRequirement(CCRenderState.normalAttrib().operationID()); + return !isRedundant(); + } + + @Override + public void operate(CCRenderState state) { + apply(state.vert.vec); + if (CCRenderState.normalAttrib().active) applyN(state.normal); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/src/main/java/codechicken/lib/vec/TransformationList.java b/src/main/java/codechicken/lib/vec/TransformationList.java new file mode 100644 index 0000000..0e5605e --- /dev/null +++ b/src/main/java/codechicken/lib/vec/TransformationList.java @@ -0,0 +1,131 @@ +package codechicken.lib.vec; + +import java.util.ArrayList; +import java.util.Iterator; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class TransformationList extends Transformation { + + private ArrayList transformations = new ArrayList(); + private Matrix4 mat; + + public TransformationList(Transformation... transforms) { + for (Transformation t : transforms) + if (t instanceof TransformationList) transformations.addAll(((TransformationList) t).transformations); + else transformations.add(t); + + compact(); + } + + public Matrix4 compile() { + if (mat == null) { + mat = new Matrix4(); + for (int i = transformations.size() - 1; i >= 0; i--) transformations.get(i).apply(mat); + } + return mat; + } + + /** + * Returns a global space matrix as opposed to an object space matrix (reverse application order) + * + * @return + */ + public Matrix4 reverseCompile() { + Matrix4 mat = new Matrix4(); + for (Transformation t : transformations) t.apply(mat); + return mat; + } + + @Override + public void apply(Vector3 vec) { + if (mat != null) mat.apply(vec); + else for (int i = 0; i < transformations.size(); i++) transformations.get(i).apply(vec); + } + + @Override + public void applyN(Vector3 normal) { + if (mat != null) mat.applyN(normal); + else for (int i = 0; i < transformations.size(); i++) transformations.get(i).applyN(normal); + } + + @Override + public void apply(Matrix4 mat) { + mat.multiply(compile()); + } + + @Override + public TransformationList with(Transformation t) { + if (t.isRedundant()) return this; + + mat = null; // matrix invalid + if (t instanceof TransformationList) transformations.addAll(((TransformationList) t).transformations); + else transformations.add(t); + + compact(); + return this; + } + + public TransformationList prepend(Transformation t) { + if (t.isRedundant()) return this; + + mat = null; // matrix invalid + if (t instanceof TransformationList) transformations.addAll(0, ((TransformationList) t).transformations); + else transformations.add(0, t); + + compact(); + return this; + } + + private void compact() { + ArrayList newList = new ArrayList(transformations.size()); + Iterator iterator = transformations.iterator(); + Transformation prev = null; + while (iterator.hasNext()) { + Transformation t = iterator.next(); + if (t.isRedundant()) continue; + + if (prev != null) { + Transformation m = prev.merge(t); + if (m == null) newList.add(prev); + else if (m.isRedundant()) t = null; + else t = m; + } + prev = t; + } + if (prev != null) newList.add(prev); + + if (newList.size() < transformations.size()) { + transformations = newList; + mat = null; + } + + if (transformations.size() > 3 && mat == null) compile(); + } + + @Override + public boolean isRedundant() { + return transformations.size() == 0; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + for (int i = transformations.size() - 1; i >= 0; i--) transformations.get(i).glApply(); + } + + @Override + public Transformation inverse() { + TransformationList rev = new TransformationList(); + for (int i = transformations.size() - 1; i >= 0; i--) rev.with(transformations.get(i).inverse()); + return rev; + } + + @Override + public String toString() { + String s = ""; + for (Transformation t : transformations) s += "\n" + t.toString(); + return s.trim(); + } +} diff --git a/src/main/java/codechicken/lib/vec/Translation.java b/src/main/java/codechicken/lib/vec/Translation.java new file mode 100644 index 0000000..f6f0a5e --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Translation.java @@ -0,0 +1,75 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import org.lwjgl.opengl.GL11; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Translation extends Transformation { + + public Vector3 vec; + + public Translation(Vector3 vec) { + this.vec = vec; + } + + public Translation(double x, double y, double z) { + this(new Vector3(x, y, z)); + } + + @Override + public void apply(Vector3 vec) { + vec.add(this.vec); + } + + @Override + public void applyN(Vector3 normal) {} + + @Override + public void apply(Matrix4 mat) { + mat.translate(vec); + } + + @Override + public Transformation at(Vector3 point) { + return this; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + GL11.glTranslated(vec.x, vec.y, vec.z); + } + + @Override + public Transformation inverse() { + return new Translation(-vec.x, -vec.y, -vec.z); + } + + @Override + public Transformation merge(Transformation next) { + if (next instanceof Translation) return new Translation(vec.copy().add(((Translation) next).vec)); + + return null; + } + + @Override + public boolean isRedundant() { + return vec.equalsT(Vector3.zero); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Translation(" + new BigDecimal(vec.x, cont) + + ", " + + new BigDecimal(vec.y, cont) + + ", " + + new BigDecimal(vec.z, cont) + + ")"; + } +} diff --git a/src/main/java/codechicken/lib/vec/VariableTransformation.java b/src/main/java/codechicken/lib/vec/VariableTransformation.java new file mode 100644 index 0000000..28669a2 --- /dev/null +++ b/src/main/java/codechicken/lib/vec/VariableTransformation.java @@ -0,0 +1,29 @@ +package codechicken.lib.vec; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public abstract class VariableTransformation extends Transformation { + + public Matrix4 mat; + + public VariableTransformation(Matrix4 mat) { + this.mat = mat; + } + + @Override + public void applyN(Vector3 normal) { + apply(normal); + } + + @Override + public void apply(Matrix4 mat) { + mat.multiply(this.mat); + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + mat.glApply(); + } +} diff --git a/src/main/java/codechicken/lib/vec/Vector3.java b/src/main/java/codechicken/lib/vec/Vector3.java new file mode 100644 index 0000000..dcc508c --- /dev/null +++ b/src/main/java/codechicken/lib/vec/Vector3.java @@ -0,0 +1,445 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import net.minecraft.entity.Entity; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Vec3; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.util.vector.Vector3f; +import org.lwjgl.util.vector.Vector4f; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.util.Copyable; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Vector3 implements Copyable { + + public static Vector3 zero = new Vector3(); + public static Vector3 one = new Vector3(1, 1, 1); + public static Vector3 center = new Vector3(0.5, 0.5, 0.5); + + public double x; + public double y; + public double z; + + public Vector3() {} + + public Vector3(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3(Vector3 vec) { + x = vec.x; + y = vec.y; + z = vec.z; + } + + public Vector3(double[] da) { + this(da[0], da[1], da[2]); + } + + public Vector3(Vec3 vec) { + x = vec.xCoord; + y = vec.yCoord; + z = vec.zCoord; + } + + public Vector3(BlockCoord coord) { + x = coord.x; + y = coord.y; + z = coord.z; + } + + public Vector3 copy() { + return new Vector3(this); + } + + public static Vector3 fromEntity(Entity e) { + return new Vector3(e.posX, e.posY, e.posZ); + } + + public static Vector3 fromEntityCenter(Entity e) { + return new Vector3(e.posX, e.posY - e.yOffset + e.height / 2, e.posZ); + } + + public static Vector3 fromTileEntity(TileEntity e) { + return new Vector3(e.xCoord, e.yCoord, e.zCoord); + } + + public static Vector3 fromTileEntityCenter(TileEntity e) { + return new Vector3(e.xCoord + 0.5, e.yCoord + 0.5, e.zCoord + 0.5); + } + + public static Vector3 fromAxes(double[] da) { + return new Vector3(da[2], da[0], da[1]); + } + + public Vector3 set(double d, double d1, double d2) { + x = d; + y = d1; + z = d2; + return this; + } + + public Vector3 set(Vector3 vec) { + x = vec.x; + y = vec.y; + z = vec.z; + return this; + } + + public double getSide(int side) { + switch (side) { + case 0: + case 1: + return y; + case 2: + case 3: + return z; + case 4: + case 5: + return x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public Vector3 setSide(int s, double v) { + switch (s) { + case 0: + case 1: + y = v; + break; + case 2: + case 3: + z = v; + break; + case 4: + case 5: + x = v; + break; + default: + throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } + + public double dotProduct(Vector3 vec) { + double d = vec.x * x + vec.y * y + vec.z * z; + + if (d > 1 && d < 1.00001) d = 1; + else if (d < -1 && d > -1.00001) d = -1; + return d; + } + + public double dotProduct(double d, double d1, double d2) { + return d * x + d1 * y + d2 * z; + } + + public Vector3 crossProduct(Vector3 vec) { + double d = y * vec.z - z * vec.y; + double d1 = z * vec.x - x * vec.z; + double d2 = x * vec.y - y * vec.x; + x = d; + y = d1; + z = d2; + return this; + } + + public Vector3 add(double d, double d1, double d2) { + x += d; + y += d1; + z += d2; + return this; + } + + public Vector3 add(Vector3 vec) { + x += vec.x; + y += vec.y; + z += vec.z; + return this; + } + + public Vector3 add(double d) { + return add(d, d, d); + } + + public Vector3 sub(Vector3 vec) { + return subtract(vec); + } + + public Vector3 subtract(Vector3 vec) { + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + public Vector3 negate(Vector3 vec) { + x = -x; + y = -y; + z = -z; + return this; + } + + public Vector3 multiply(double d) { + x *= d; + y *= d; + z *= d; + return this; + } + + public Vector3 multiply(Vector3 f) { + x *= f.x; + y *= f.y; + z *= f.z; + return this; + } + + public Vector3 multiply(double fx, double fy, double fz) { + x *= fx; + y *= fy; + z *= fz; + return this; + } + + public double mag() { + return Math.sqrt(x * x + y * y + z * z); + } + + public double magSquared() { + return x * x + y * y + z * z; + } + + public Vector3 normalize() { + double d = mag(); + if (d != 0) { + multiply(1 / d); + } + return this; + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Vector3(" + new BigDecimal(x, cont) + + ", " + + new BigDecimal(y, cont) + + ", " + + new BigDecimal(z, cont) + + ")"; + } + + public Vector3 perpendicular() { + if (z == 0) return zCrossProduct(); + return xCrossProduct(); + } + + public Vector3 xCrossProduct() { + double d = z; + double d1 = -y; + x = 0; + y = d; + z = d1; + return this; + } + + public Vector3 zCrossProduct() { + double d = y; + double d1 = -x; + x = d; + y = d1; + z = 0; + return this; + } + + public Vector3 yCrossProduct() { + double d = -z; + double d1 = x; + x = d; + y = 0; + z = d1; + return this; + } + + public Vector3 rotate(double angle, Vector3 axis) { + Quat.aroundAxis(axis.copy().normalize(), angle).rotate(this); + return this; + } + + public Vector3 rotate(Quat rotator) { + rotator.rotate(this); + return this; + } + + public Vec3 toVec3D() { + return Vec3.createVectorHelper(x, y, z); + } + + public double angle(Vector3 vec) { + return Math.acos(copy().normalize().dotProduct(vec.copy().normalize())); + } + + public boolean isZero() { + return x == 0 && y == 0 && z == 0; + } + + public boolean isAxial() { + return x == 0 ? (y == 0 || z == 0) : (y == 0 && z == 0); + } + + @SideOnly(Side.CLIENT) + public Vector3f vector3f() { + return new Vector3f((float) x, (float) y, (float) z); + } + + @SideOnly(Side.CLIENT) + public Vector4f vector4f() { + return new Vector4f((float) x, (float) y, (float) z, 1); + } + + @SideOnly(Side.CLIENT) + public void glVertex() { + GL11.glVertex3d(x, y, z); + } + + public Vector3 YZintercept(Vector3 end, double px) { + double dx = end.x - x; + double dy = end.y - y; + double dz = end.z - z; + + if (dx == 0) return null; + + double d = (px - x) / dx; + if (MathHelper.between(-1E-5, d, 1E-5)) return this; + + if (!MathHelper.between(0, d, 1)) return null; + + x = px; + y += d * dy; + z += d * dz; + return this; + } + + public Vector3 XZintercept(Vector3 end, double py) { + double dx = end.x - x; + double dy = end.y - y; + double dz = end.z - z; + + if (dy == 0) return null; + + double d = (py - y) / dy; + if (MathHelper.between(-1E-5, d, 1E-5)) return this; + + if (!MathHelper.between(0, d, 1)) return null; + + x += d * dx; + y = py; + z += d * dz; + return this; + } + + public Vector3 XYintercept(Vector3 end, double pz) { + double dx = end.x - x; + double dy = end.y - y; + double dz = end.z - z; + + if (dz == 0) return null; + + double d = (pz - z) / dz; + if (MathHelper.between(-1E-5, d, 1E-5)) return this; + + if (!MathHelper.between(0, d, 1)) return null; + + x += d * dx; + y += d * dy; + z = pz; + return this; + } + + public Vector3 negate() { + x = -x; + y = -y; + z = -z; + return this; + } + + public Translation translation() { + return new Translation(this); + } + + public double scalarProject(Vector3 b) { + double l = b.mag(); + return l == 0 ? 0 : dotProduct(b) / l; + } + + public Vector3 project(Vector3 b) { + double l = b.magSquared(); + if (l == 0) { + set(0, 0, 0); + return this; + } + double m = dotProduct(b) / l; + set(b).multiply(m); + return this; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector3)) return false; + Vector3 v = (Vector3) o; + return x == v.x && y == v.y && z == v.z; + } + + /** + * Equals method with tolerance + * + * @return true if this is equal to v within +-1E-5 + */ + public boolean equalsT(Vector3 v) { + return MathHelper.between(x - 1E-5, v.x, x + 1E-5) && MathHelper.between(y - 1E-5, v.y, y + 1E-5) + && MathHelper.between(z - 1E-5, v.z, z + 1E-5); + } + + public Vector3 apply(Transformation t) { + t.apply(this); + return this; + } + + public Vector3 $tilde() { + return normalize(); + } + + public Vector3 unary_$tilde() { + return normalize(); + } + + public Vector3 $plus(Vector3 v) { + return add(v); + } + + public Vector3 $minus(Vector3 v) { + return subtract(v); + } + + public Vector3 $times(double d) { + return multiply(d); + } + + public Vector3 $div(double d) { + return multiply(1 / d); + } + + public Vector3 $times(Vector3 v) { + return crossProduct(v); + } + + public double $dot$times(Vector3 v) { + return dotProduct(v); + } +} diff --git a/src/main/java/codechicken/lib/world/ChunkExtension.java b/src/main/java/codechicken/lib/world/ChunkExtension.java new file mode 100644 index 0000000..d376dd0 --- /dev/null +++ b/src/main/java/codechicken/lib/world/ChunkExtension.java @@ -0,0 +1,64 @@ +package codechicken.lib.world; + +import java.util.HashSet; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.Packet; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.chunk.Chunk; + +public abstract class ChunkExtension { + + public final Chunk chunk; + public final ChunkCoordIntPair coord; + public final WorldExtension world; + public HashSet watchedPlayers; + + public ChunkExtension(Chunk chunk, WorldExtension world) { + this.chunk = chunk; + coord = chunk.getChunkCoordIntPair(); + this.world = world; + watchedPlayers = new HashSet(); + } + + public void loadData(NBTTagCompound tag) {} + + public void saveData(NBTTagCompound tag) {} + + public void load() {} + + public void unload() {} + + public final void sendPacketToPlayers(Packet packet) { + for (EntityPlayerMP player : watchedPlayers) player.playerNetServerHandler.sendPacket(packet); + } + + public final void watchPlayer(EntityPlayerMP player) { + watchedPlayers.add(player); + onWatchPlayer(player); + } + + public void onWatchPlayer(EntityPlayerMP player) {} + + public final void unwatchPlayer(EntityPlayerMP player) { + watchedPlayers.remove(player); + onUnWatchPlayer(player); + } + + public void onUnWatchPlayer(EntityPlayerMP player) {} + + public void sendUpdatePackets() {} + + @Override + public int hashCode() { + return coord.chunkXPos ^ coord.chunkZPos; + } + + @Override + public boolean equals(Object o) { + return (o instanceof ChunkExtension && ((ChunkExtension) o).coord.equals(coord)) + || (o instanceof ChunkCoordIntPair && coord.equals(o)) + || (o instanceof Long && (Long) o == (((long) coord.chunkXPos) << 32 | coord.chunkZPos)); + } +} diff --git a/src/main/java/codechicken/lib/world/IChunkLoadTile.java b/src/main/java/codechicken/lib/world/IChunkLoadTile.java new file mode 100644 index 0000000..5e914df --- /dev/null +++ b/src/main/java/codechicken/lib/world/IChunkLoadTile.java @@ -0,0 +1,13 @@ +package codechicken.lib.world; + +/** + * Provides a callback for tile entities when a chunk is loaded, as an alternative to validate when the chunk hasn't + * been added to the world. To hook all world join/seperate events. Use this, TileEntity.validate with a + * worldObj.blockExists check, TileEntity.onChunkUnload and TileEntity.invalidate Be sure to call + * TileChunkLoadHook.init() from your mod during initialisation You could easily implement this in your own mod, but + * providing it here reduces the number of times the chunkTileEntityMap needs to be iterated + */ +public interface IChunkLoadTile { + + public void onChunkLoad(); +} diff --git a/src/main/java/codechicken/lib/world/TileChunkLoadHook.java b/src/main/java/codechicken/lib/world/TileChunkLoadHook.java new file mode 100644 index 0000000..1ca6bba --- /dev/null +++ b/src/main/java/codechicken/lib/world/TileChunkLoadHook.java @@ -0,0 +1,28 @@ +package codechicken.lib.world; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.tileentity.TileEntity; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.ChunkEvent; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; + +public class TileChunkLoadHook { + + private static boolean init; + + public static void init() { + if (init) return; + init = true; + + MinecraftForge.EVENT_BUS.register(new TileChunkLoadHook()); + } + + @SubscribeEvent + public void onChunkLoad(ChunkEvent.Load event) { + List list = new ArrayList(event.getChunk().chunkTileEntityMap.values()); + for (TileEntity t : list) if (t instanceof IChunkLoadTile) ((IChunkLoadTile) t).onChunkLoad(); + } +} diff --git a/src/main/java/codechicken/lib/world/WorldExtension.java b/src/main/java/codechicken/lib/world/WorldExtension.java new file mode 100644 index 0000000..e6ff496 --- /dev/null +++ b/src/main/java/codechicken/lib/world/WorldExtension.java @@ -0,0 +1,76 @@ +package codechicken.lib.world; + +import java.util.HashMap; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +public abstract class WorldExtension { + + public final World world; + public HashMap chunkMap = new HashMap(); + + public WorldExtension(World world) { + this.world = world; + } + + public void load() {} + + public void unload() {} + + public void save() {} + + public void preTick() {} + + public void postTick() {} + + protected final void addChunk(ChunkExtension extension) { + chunkMap.put(extension.chunk, extension); + } + + protected final void loadChunk(Chunk chunk) { + chunkMap.get(chunk).load(); + } + + protected final void unloadChunk(Chunk chunk) { + if (chunkMap.get(chunk) != null) chunkMap.get(chunk).unload(); + } + + protected final void loadChunkData(Chunk chunk, NBTTagCompound tag) { + chunkMap.get(chunk).loadData(tag); + } + + protected final void saveChunkData(Chunk chunk, NBTTagCompound tag) { + final ChunkExtension extension = chunkMap.get(chunk); + if (extension != null) extension.saveData(tag); + } + + protected final void remChunk(Chunk chunk) { + chunkMap.remove(chunk); + } + + protected final void watchChunk(Chunk chunk, EntityPlayerMP player) { + chunkMap.get(chunk).watchPlayer(player); + } + + protected final void unwatchChunk(Chunk chunk, EntityPlayerMP player) { + ChunkExtension extension = chunkMap.get(chunk); + if (extension != null) extension.unwatchPlayer(player); + } + + protected final void sendChunkUpdates(Chunk chunk) { + chunkMap.get(chunk).sendUpdatePackets(); + } + + public boolean containsChunk(Chunk chunk) { + return chunkMap.containsKey(chunk); + } + + public ChunkExtension getChunkExtension(int chunkXPos, int chunkZPos) { + if (!world.blockExists(chunkXPos << 4, 128, chunkZPos << 4)) return null; + + return chunkMap.get(world.getChunkFromChunkCoords(chunkXPos, chunkZPos)); + } +} diff --git a/src/main/java/codechicken/lib/world/WorldExtensionInstantiator.java b/src/main/java/codechicken/lib/world/WorldExtensionInstantiator.java new file mode 100644 index 0000000..38a4582 --- /dev/null +++ b/src/main/java/codechicken/lib/world/WorldExtensionInstantiator.java @@ -0,0 +1,17 @@ +package codechicken.lib.world; + +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +public abstract class WorldExtensionInstantiator { + + public int instantiatorID; + + public abstract WorldExtension createWorldExtension(World world); + + public abstract ChunkExtension createChunkExtension(Chunk chunk, WorldExtension world); + + public WorldExtension getExtension(World world) { + return WorldExtensionManager.getWorldExtension(world, instantiatorID); + } +} diff --git a/src/main/java/codechicken/lib/world/WorldExtensionManager.java b/src/main/java/codechicken/lib/world/WorldExtensionManager.java new file mode 100644 index 0000000..8971ff0 --- /dev/null +++ b/src/main/java/codechicken/lib/world/WorldExtensionManager.java @@ -0,0 +1,169 @@ +package codechicken.lib.world; + +import java.util.ArrayList; +import java.util.HashMap; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.EmptyChunk; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.ChunkDataEvent; +import net.minecraftforge.event.world.ChunkEvent; +import net.minecraftforge.event.world.ChunkWatchEvent.UnWatch; +import net.minecraftforge.event.world.ChunkWatchEvent.Watch; +import net.minecraftforge.event.world.WorldEvent; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class WorldExtensionManager { + + public static class WorldExtensionEventHandler { + + @SubscribeEvent + public void onChunkDataLoad(ChunkDataEvent.Load event) { + if (!worldMap.containsKey(event.world)) WorldExtensionManager.onWorldLoad(event.world); + + createChunkExtension(event.world, event.getChunk()); + + for (WorldExtension extension : worldMap.get(event.world)) + extension.loadChunkData(event.getChunk(), event.getData()); + } + + @SubscribeEvent + public void onChunkDataSave(ChunkDataEvent.Save event) { + for (WorldExtension extension : worldMap.get(event.world)) + extension.saveChunkData(event.getChunk(), event.getData()); + + if (!event.getChunk().isChunkLoaded) removeChunk(event.world, event.getChunk()); + } + + @SubscribeEvent + public void onChunkLoad(ChunkEvent.Load event) { + if (!worldMap.containsKey(event.world)) WorldExtensionManager.onWorldLoad(event.world); + + createChunkExtension(event.world, event.getChunk()); + + for (WorldExtension extension : worldMap.get(event.world)) extension.loadChunk(event.getChunk()); + } + + @SubscribeEvent + public void onChunkUnLoad(ChunkEvent.Unload event) { + if (event.getChunk() instanceof EmptyChunk) return; + + for (WorldExtension extension : worldMap.get(event.world)) extension.unloadChunk(event.getChunk()); + + if (event.world.isRemote) removeChunk(event.world, event.getChunk()); + } + + @SubscribeEvent + public void onWorldSave(WorldEvent.Save event) { + if (worldMap.containsKey(event.world)) + for (WorldExtension extension : worldMap.get(event.world)) extension.save(); + } + + @SubscribeEvent + public void onWorldLoad(WorldEvent.Load event) { + if (!worldMap.containsKey(event.world)) WorldExtensionManager.onWorldLoad(event.world); + } + + @SubscribeEvent + public void onWorldUnLoad(WorldEvent.Unload event) { + if (worldMap.containsKey(event.world)) // because force closing unloads a world twice + for (WorldExtension extension : worldMap.remove(event.world)) extension.unload(); + } + + @SubscribeEvent + public void onChunkWatch(Watch event) { + WorldExtension[] extensions = worldMap.get(event.player.worldObj); + + if (extensions != null) { + Chunk chunk = event.player.worldObj + .getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos); + for (WorldExtension extension : extensions) extension.watchChunk(chunk, event.player); + } + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onChunkUnWatch(UnWatch event) { + WorldExtension[] extensions = worldMap.get(event.player.worldObj); + + if (extensions != null) { + Chunk chunk = event.player.worldObj + .getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos); + for (WorldExtension extension : extensions) extension.unwatchChunk(chunk, event.player); + } + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void clientTick(TickEvent.ClientTickEvent event) { + World world = Minecraft.getMinecraft().theWorld; + if (worldMap.containsKey(world)) if (event.phase == TickEvent.Phase.START) preTick(world); + else postTick(world); + } + + @SubscribeEvent + public void serverTick(TickEvent.WorldTickEvent event) { + if (!worldMap.containsKey(event.world)) WorldExtensionManager.onWorldLoad(event.world); + + if (event.phase == TickEvent.Phase.START) preTick(event.world); + else postTick(event.world); + } + } + + private static boolean initialised; + private static ArrayList extensionIntialisers = new ArrayList<>(); + + public static void registerWorldExtension(WorldExtensionInstantiator init) { + if (!initialised) init(); + + init.instantiatorID = extensionIntialisers.size(); + extensionIntialisers.add(init); + } + + private static void init() { + initialised = true; + MinecraftForge.EVENT_BUS.register(new WorldExtensionEventHandler()); + FMLCommonHandler.instance().bus().register(new WorldExtensionEventHandler()); + } + + private static HashMap worldMap = new HashMap<>(); + + private static void onWorldLoad(World world) { + WorldExtension[] extensions = new WorldExtension[extensionIntialisers.size()]; + for (int i = 0; i < extensions.length; i++) + extensions[i] = extensionIntialisers.get(i).createWorldExtension(world); + + worldMap.put(world, extensions); + + for (WorldExtension extension : extensions) extension.load(); + } + + private static void createChunkExtension(World world, Chunk chunk) { + WorldExtension[] extensions = worldMap.get(world); + for (int i = 0; i < extensionIntialisers.size(); i++) if (!extensions[i].containsChunk(chunk)) + extensions[i].addChunk(extensionIntialisers.get(i).createChunkExtension(chunk, extensions[i])); + } + + private static void removeChunk(World world, Chunk chunk) { + for (WorldExtension extension : worldMap.get(world)) extension.remChunk(chunk); + } + + private static void preTick(World world) { + for (WorldExtension extension : worldMap.get(world)) extension.preTick(); + } + + private static void postTick(World world) { + for (WorldExtension extension : worldMap.get(world)) extension.postTick(); + } + + public static WorldExtension getWorldExtension(World world, int instantiatorID) { + return worldMap.get(world)[instantiatorID]; + } +} diff --git a/src/main/java/codechicken/obfuscator/ConstantObfuscator.java b/src/main/java/codechicken/obfuscator/ConstantObfuscator.java new file mode 100644 index 0000000..ec44e11 --- /dev/null +++ b/src/main/java/codechicken/obfuscator/ConstantObfuscator.java @@ -0,0 +1,72 @@ +package codechicken.obfuscator; + +import static org.objectweb.asm.tree.AbstractInsnNode.LDC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN; + +import java.util.LinkedList; +import java.util.List; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import codechicken.lib.asm.ObfMapping; + +public class ConstantObfuscator implements Opcodes { + + public ObfRemapper obf; + public List descCalls = new LinkedList(); + public List classCalls = new LinkedList(); + + public ConstantObfuscator(ObfRemapper obf, String[] a_classCalls, String[] a_descCalls) { + this.obf = obf; + for (String callDesc : a_classCalls) classCalls.add(ObfMapping.fromDesc(callDesc)); + + for (String callDesc : a_descCalls) descCalls.add(ObfMapping.fromDesc(callDesc)); + } + + public void transform(ClassNode cnode) { + for (MethodNode method : cnode.methods) + for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) + obfuscateInsnSeq(insn); + } + + private void obfuscateInsnSeq(AbstractInsnNode insn) { + if (matchesClass(insn)) { + LdcInsnNode node1 = (LdcInsnNode) insn; + node1.cst = obf.map((String) node1.cst); + } + if (matchesDesc(insn)) { + LdcInsnNode node1 = (LdcInsnNode) insn; + LdcInsnNode node2 = (LdcInsnNode) node1.getNext(); + LdcInsnNode node3 = (LdcInsnNode) node2.getNext(); + ObfMapping mapping = new ObfMapping((String) node1.cst, (String) node2.cst, (String) node3.cst).map(obf); + node1.cst = mapping.s_owner; + node2.cst = mapping.s_name; + node3.cst = mapping.s_desc; + } + } + + private boolean matchesClass(AbstractInsnNode insn) { + if (insn.getType() != LDC_INSN) return false; + insn = insn.getNext(); + if (insn == null || insn.getType() != METHOD_INSN) return false; + for (ObfMapping m : classCalls) if (m.matches((MethodInsnNode) insn)) return true; + return false; + } + + private boolean matchesDesc(AbstractInsnNode insn) { + if (insn.getType() != LDC_INSN) return false; + insn = insn.getNext(); + if (insn == null || insn.getType() != LDC_INSN) return false; + insn = insn.getNext(); + if (insn == null || insn.getType() != LDC_INSN) return false; + insn = insn.getNext(); + if (insn == null || insn.getType() != METHOD_INSN) return false; + for (ObfMapping m : descCalls) if (m.matches((MethodInsnNode) insn)) return true; + return false; + } +} diff --git a/src/codechicken/obfuscator/DummyOutputStream.java b/src/main/java/codechicken/obfuscator/DummyOutputStream.java similarity index 61% rename from src/codechicken/obfuscator/DummyOutputStream.java rename to src/main/java/codechicken/obfuscator/DummyOutputStream.java index 50093ef..b5b4e8c 100644 --- a/src/codechicken/obfuscator/DummyOutputStream.java +++ b/src/main/java/codechicken/obfuscator/DummyOutputStream.java @@ -2,12 +2,10 @@ import java.io.OutputStream; -public class DummyOutputStream extends OutputStream -{ +public class DummyOutputStream extends OutputStream { + public static DummyOutputStream instance = new DummyOutputStream(); @Override - public void write(int b) - { - } + public void write(int b) {} } diff --git a/src/codechicken/obfuscator/IHeirachyEvaluator.java b/src/main/java/codechicken/obfuscator/IHeirachyEvaluator.java similarity index 93% rename from src/codechicken/obfuscator/IHeirachyEvaluator.java rename to src/main/java/codechicken/obfuscator/IHeirachyEvaluator.java index 4c43444..906ed92 100644 --- a/src/codechicken/obfuscator/IHeirachyEvaluator.java +++ b/src/main/java/codechicken/obfuscator/IHeirachyEvaluator.java @@ -4,8 +4,8 @@ import codechicken.obfuscator.ObfuscationMap.ObfuscationEntry; -public interface IHeirachyEvaluator -{ +public interface IHeirachyEvaluator { + /** * @param desc The mapping descriptor of the class to evaluate heirachy for * @return A list of parents (srg or obf names) diff --git a/src/codechicken/obfuscator/ILogStreams.java b/src/main/java/codechicken/obfuscator/ILogStreams.java similarity index 78% rename from src/codechicken/obfuscator/ILogStreams.java rename to src/main/java/codechicken/obfuscator/ILogStreams.java index 0992103..31c7e95 100644 --- a/src/codechicken/obfuscator/ILogStreams.java +++ b/src/main/java/codechicken/obfuscator/ILogStreams.java @@ -2,8 +2,9 @@ import java.io.PrintStream; -public interface ILogStreams -{ +public interface ILogStreams { + public PrintStream err(); + public PrintStream out(); } diff --git a/src/codechicken/obfuscator/ObfDirection.java b/src/main/java/codechicken/obfuscator/ObfDirection.java similarity index 62% rename from src/codechicken/obfuscator/ObfDirection.java rename to src/main/java/codechicken/obfuscator/ObfDirection.java index 3405be2..8c2732e 100644 --- a/src/codechicken/obfuscator/ObfDirection.java +++ b/src/main/java/codechicken/obfuscator/ObfDirection.java @@ -3,32 +3,28 @@ import codechicken.lib.asm.ObfMapping; import codechicken.obfuscator.ObfuscationMap.ObfuscationEntry; -public class ObfDirection -{ +public class ObfDirection { + public boolean obfuscate; public boolean srg; public boolean srg_cst; - - public ObfDirection setObfuscate(boolean obfuscate) - { + + public ObfDirection setObfuscate(boolean obfuscate) { this.obfuscate = obfuscate; return this; } - - public ObfDirection setSearge(boolean srg) - { + + public ObfDirection setSearge(boolean srg) { this.srg = srg; return this; } - public ObfDirection setSeargeConstants(boolean srg_cst) - { + public ObfDirection setSeargeConstants(boolean srg_cst) { this.srg_cst = srg_cst; return this; } - public ObfMapping obfuscate(ObfuscationEntry map) - { + public ObfMapping obfuscate(ObfuscationEntry map) { return srg ? map.srg : obfuscate ? map.obf : map.mcp; } } diff --git a/src/main/java/codechicken/obfuscator/ObfRemapper.java b/src/main/java/codechicken/obfuscator/ObfRemapper.java new file mode 100644 index 0000000..8d46a7d --- /dev/null +++ b/src/main/java/codechicken/obfuscator/ObfRemapper.java @@ -0,0 +1,71 @@ +package codechicken.obfuscator; + +import org.objectweb.asm.commons.Remapper; + +import codechicken.obfuscator.ObfuscationMap.ObfuscationEntry; + +public class ObfRemapper extends Remapper { + + public final ObfuscationMap obf; + public ObfDirection dir; + + public ObfRemapper(ObfuscationMap obf, ObfDirection dir) { + this.obf = obf; + this.dir = dir; + } + + @Override + public String map(String name) { + if (name.indexOf('$') >= 0) + return map(name.substring(0, name.indexOf('$'))) + name.substring(name.indexOf('$')); + + ObfuscationEntry map; + if (dir.obfuscate) map = obf.lookupMcpClass(name); + else map = obf.lookupObfClass(name); + + if (map != null) return dir.obfuscate(map).s_owner; + + return name; + } + + @Override + public String mapFieldName(String owner, String name, String desc) { + ObfuscationEntry map; + if (dir.obfuscate) map = obf.lookupMcpField(owner, name); + else map = obf.lookupObfField(owner, name); + + if (map == null) map = obf.lookupSrgField(owner, name); + + if (map != null) return dir.obfuscate(map).s_name; + + return name; + } + + @Override + public String mapMethodName(String owner, String name, String desc) { + if (owner.length() == 0 || owner.charAt(0) == '[') return name; + + ObfuscationEntry map; + if (dir.obfuscate) map = obf.lookupMcpMethod(owner, name, desc); + else map = obf.lookupObfMethod(owner, name, desc); + + if (map == null) map = obf.lookupSrg(name); + + if (map != null) return dir.obfuscate(map).s_name; + + return name; + } + + @Override + public Object mapValue(Object cst) { + if (cst instanceof String) { + if (dir.srg_cst) { + ObfuscationEntry map = obf.lookupSrg((String) cst); + if (map != null) return dir.obfuscate(map).s_name; + } + return cst; + } + + return super.mapValue(cst); + } +} diff --git a/src/codechicken/obfuscator/ObfuscationMap.java b/src/main/java/codechicken/obfuscator/ObfuscationMap.java similarity index 60% rename from src/codechicken/obfuscator/ObfuscationMap.java rename to src/main/java/codechicken/obfuscator/ObfuscationMap.java index 3350e5a..474b911 100644 --- a/src/codechicken/obfuscator/ObfuscationMap.java +++ b/src/main/java/codechicken/obfuscator/ObfuscationMap.java @@ -12,157 +12,130 @@ import codechicken.lib.asm.ObfMapping; -public class ObfuscationMap -{ - public class ObfuscationEntry - { +public class ObfuscationMap { + + public class ObfuscationEntry { + public final ObfMapping obf; public final ObfMapping srg; public final ObfMapping mcp; - - public ObfuscationEntry(ObfMapping obf, ObfMapping srg, ObfMapping mcp) - { + + public ObfuscationEntry(ObfMapping obf, ObfMapping srg, ObfMapping mcp) { this.obf = obf; this.srg = srg; this.mcp = mcp; } } - - private class ClassEntry extends ObfuscationEntry - { + + private class ClassEntry extends ObfuscationEntry { + public Map mcpMap = new HashMap(); public Map srgMap = new HashMap(); public Map obfMap = new HashMap(); - - public ClassEntry(String obf, String srg) - { - super(new ObfMapping(obf, "", ""), - new ObfMapping(srg, "", ""), - new ObfMapping(srg, "", "")); + + public ClassEntry(String obf, String srg) { + super(new ObfMapping(obf, "", ""), new ObfMapping(srg, "", ""), new ObfMapping(srg, "", "")); } - - public ObfuscationEntry addEntry(ObfMapping obf_desc, ObfMapping srg_desc) - { + + public ObfuscationEntry addEntry(ObfMapping obf_desc, ObfMapping srg_desc) { ObfuscationEntry entry = new ObfuscationEntry(obf_desc, srg_desc, srg_desc.copy()); obfMap.put(obf_desc.s_name.concat(obf_desc.s_desc), entry); srgMap.put(srg_desc.s_name, entry); - - if(srg_desc.s_name.startsWith("field_") || srg_desc.s_name.startsWith("func_")) + + if (srg_desc.s_name.startsWith("field_") || srg_desc.s_name.startsWith("func_")) srgMemberMap.put(srg_desc.s_name, entry); - + return entry; } - public void inheritFrom(ClassEntry p) - { + public void inheritFrom(ClassEntry p) { inherit(obfMap, p.obfMap); inherit(srgMap, p.srgMap); inherit(mcpMap, p.mcpMap); } - - private void inherit(Map child, Map parent) - { - for(Entry e : parent.entrySet()) - if(!child.containsKey(e.getKey())) - child.put(e.getKey(), e.getValue()); + + private void inherit(Map child, Map parent) { + for (Entry e : parent.entrySet()) + if (!child.containsKey(e.getKey())) child.put(e.getKey(), e.getValue()); } } - + private Map srgMap = new HashMap(); private Map obfMap = new HashMap(); private ArrayListMultimap srgMemberMap = ArrayListMultimap.create(); - + private IHeirachyEvaluator heirachyEvaluator; private HashSet mappedClasses = new HashSet(); public ILogStreams log = SystemLogStreams.inst; - - public ObfuscationMap setHeirachyEvaluator(IHeirachyEvaluator eval) - { + + public ObfuscationMap setHeirachyEvaluator(IHeirachyEvaluator eval) { heirachyEvaluator = eval; return this; } - - public ObfuscationMap setLog(ILogStreams log) - { + + public ObfuscationMap setLog(ILogStreams log) { this.log = log; return this; } - - public ObfuscationEntry addClass(String obf, String srg) - { + + public ObfuscationEntry addClass(String obf, String srg) { return addEntry(new ObfMapping(obf, "", ""), new ObfMapping(srg, "", "")); } - - public ObfuscationEntry addField(String obf_owner, String obf_name, String srg_owner, String srg_name) - { - return addEntry(new ObfMapping(obf_owner, obf_name, ""), - new ObfMapping(srg_owner, srg_name, "")); + + public ObfuscationEntry addField(String obf_owner, String obf_name, String srg_owner, String srg_name) { + return addEntry(new ObfMapping(obf_owner, obf_name, ""), new ObfMapping(srg_owner, srg_name, "")); } - - public ObfuscationEntry addMethod(String obf_owner, String obf_name, String obf_desc, String srg_owner, String srg_name, String srg_desc) - { - return addEntry(new ObfMapping(obf_owner, obf_name, obf_desc), - new ObfMapping(srg_owner, srg_name, srg_desc)); + + public ObfuscationEntry addMethod(String obf_owner, String obf_name, String obf_desc, String srg_owner, + String srg_name, String srg_desc) { + return addEntry(new ObfMapping(obf_owner, obf_name, obf_desc), new ObfMapping(srg_owner, srg_name, srg_desc)); } - - public ObfuscationEntry addEntry(ObfMapping obf_desc, ObfMapping srg_desc) - { + + public ObfuscationEntry addEntry(ObfMapping obf_desc, ObfMapping srg_desc) { ClassEntry e = srgMap.get(srg_desc.s_owner); - if(e == null) - { + if (e == null) { e = new ClassEntry(obf_desc.s_owner, srg_desc.s_owner); obfMap.put(obf_desc.s_owner, e); srgMap.put(srg_desc.s_owner, e); } - if(obf_desc.s_name.length() > 0) - return e.addEntry(obf_desc, srg_desc); - + if (obf_desc.s_name.length() > 0) return e.addEntry(obf_desc, srg_desc); + return e; } - - public void addMcpName(String srg_name, String mcp_name) - { + + public void addMcpName(String srg_name, String mcp_name) { List entries = srgMemberMap.get(srg_name); - if(entries.isEmpty()) - { - log.err().println("Tried to add mcp name ("+mcp_name+") for unknown srg key ("+srg_name+")"); + if (entries.isEmpty()) { + log.err().println("Tried to add mcp name (" + mcp_name + ") for unknown srg key (" + srg_name + ")"); return; } - for(ObfuscationEntry entry : entries) - { + for (ObfuscationEntry entry : entries) { entry.mcp.s_name = mcp_name; srgMap.get(entry.srg.s_owner).mcpMap.put(entry.mcp.s_name.concat(entry.mcp.s_desc), entry); } } - - public ObfuscationEntry lookupSrg(String srg_key) - { + + public ObfuscationEntry lookupSrg(String srg_key) { List list = srgMemberMap.get(srg_key); return list.isEmpty() ? null : list.get(0); } - - public ObfuscationEntry lookupMcpClass(String name) - { + + public ObfuscationEntry lookupMcpClass(String name) { return srgMap.get(name); } - public ObfuscationEntry lookupObfClass(String name) - { + public ObfuscationEntry lookupObfClass(String name) { return obfMap.get(name); } - public ObfuscationEntry lookupMcpField(String owner, String name) - { + public ObfuscationEntry lookupMcpField(String owner, String name) { return lookupMcpMethod(owner, name, ""); } - public ObfuscationEntry lookupSrgField(String owner, String name) - { - if(name.startsWith("field_")) - { + public ObfuscationEntry lookupSrgField(String owner, String name) { + if (name.startsWith("field_")) { ObfuscationEntry e = lookupSrg(name); - if(e != null) - return e; + if (e != null) return e; } evaluateHeirachy(owner); @@ -170,174 +143,144 @@ public ObfuscationEntry lookupSrgField(String owner, String name) return e == null ? null : e.srgMap.get(name); } - public ObfuscationEntry lookupObfField(String owner, String name) - { + public ObfuscationEntry lookupObfField(String owner, String name) { return lookupObfMethod(owner, name, ""); } - public ObfuscationEntry lookupMcpMethod(String owner, String name, String desc) - { + public ObfuscationEntry lookupMcpMethod(String owner, String name, String desc) { evaluateHeirachy(owner); ClassEntry e = srgMap.get(owner); return e == null ? null : e.mcpMap.get(name.concat(desc)); } - public ObfuscationEntry lookupObfMethod(String owner, String name, String desc) - { + public ObfuscationEntry lookupObfMethod(String owner, String name, String desc) { evaluateHeirachy(owner); ClassEntry e = obfMap.get(owner); return e == null ? null : e.obfMap.get(name.concat(desc)); } - - private boolean isMapped(ObfuscationEntry desc) - { + + private boolean isMapped(ObfuscationEntry desc) { return mappedClasses.contains(desc.srg.s_owner); } - - private ObfuscationEntry getOrCreateClassEntry(String name) - { + + private ObfuscationEntry getOrCreateClassEntry(String name) { ObfuscationEntry e = lookupObfClass(name); - if(e == null) - e = lookupMcpClass(name); - if(e == null) - e = addClass(name, name);//if the class isn't in obf or srg maps, it must be a custom mod class with no name change. + if (e == null) e = lookupMcpClass(name); + if (e == null) e = addClass(name, name); // if the class isn't in obf or srg maps, it must be a custom mod class + // with no name change. return e; } - public ObfuscationEntry evaluateHeirachy(String name) - { + public ObfuscationEntry evaluateHeirachy(String name) { ObfuscationEntry desc = getOrCreateClassEntry(name); - if(isMapped(desc)) - return desc; + if (isMapped(desc)) return desc; mappedClasses.add(desc.srg.s_owner); - if(heirachyEvaluator == null) + if (heirachyEvaluator == null) throw new IllegalArgumentException("Cannot call method/field mappings if a heirachy evaluator is not set."); - - if(!heirachyEvaluator.isLibClass(desc)) - { + + if (!heirachyEvaluator.isLibClass(desc)) { List parents = heirachyEvaluator.getParents(desc); - if(parents == null) - log.err().println("Could not find class: "+desc.srg.s_owner); - else - for(String parent : parents) - inherit(desc, evaluateHeirachy(parent)); + if (parents == null) log.err().println("Could not find class: " + desc.srg.s_owner); + else for (String parent : parents) inherit(desc, evaluateHeirachy(parent)); } - + return desc; } - public void inherit(ObfuscationEntry desc, ObfuscationEntry p_desc) - { + public void inherit(ObfuscationEntry desc, ObfuscationEntry p_desc) { inherit(desc.srg.s_owner, p_desc.srg.s_owner); } - public void inherit(String name, String parent) - { + public void inherit(String name, String parent) { ClassEntry e = srgMap.get(name); - if(e == null) - throw new IllegalStateException("Tried to inerit to an undefined class: "+name+" extends "+parent); + if (e == null) + throw new IllegalStateException("Tried to inerit to an undefined class: " + name + " extends " + parent); ClassEntry p = srgMap.get(parent); - if(p == null) - throw new IllegalStateException("Tried to inerit from undefired parent: "+name+" extends "+parent); + if (p == null) + throw new IllegalStateException("Tried to inerit from undefired parent: " + name + " extends " + parent); e.inheritFrom(p); } - - public void parseMappings(File[] mappings) - { + + public void parseMappings(File[] mappings) { parseSRGS(mappings[0]); parseCSV(mappings[1]); parseCSV(mappings[2]); } - - public static String[] splitLast(String s, char c) - { + + public static String[] splitLast(String s, char c) { int i = s.lastIndexOf(c); - return new String[]{s.substring(0, i), s.substring(i+1)}; + return new String[] { s.substring(0, i), s.substring(i + 1) }; } - - public static String[] split4(String s, char c) - { + + public static String[] split4(String s, char c) { String[] split = new String[4]; int i2 = s.indexOf(c); split[0] = s.substring(0, i2); - int i = i2+1; + int i = i2 + 1; i2 = s.indexOf(c, i); split[1] = s.substring(i, i2); - i = i2+1; + i = i2 + 1; i2 = s.indexOf(c, i); split[2] = s.substring(i, i2); - i = i2+1; + i = i2 + 1; i2 = s.indexOf(c, i); split[3] = s.substring(i); return split; } - private void parseSRGS(File srgs) - { - log.out().println("Parsing "+srgs.getName()); - - Function function = new Function() - { + private void parseSRGS(File srgs) { + log.out().println("Parsing " + srgs.getName()); + + Function function = new Function() { + @Override - public Void apply(String line) - { + public Void apply(String line) { int hpos = line.indexOf('#'); - if(hpos > 0) - line = line.substring(0, hpos).trim(); - if(line.startsWith("CL: ")) - { + if (hpos > 0) line = line.substring(0, hpos).trim(); + if (line.startsWith("CL: ")) { String[] params = splitLast(line.substring(4), ' '); addClass(params[0], params[1]); - } - else if(line.startsWith("FD: ")) - { + } else if (line.startsWith("FD: ")) { String[] params = splitLast(line.substring(4), ' '); String[] p1 = splitLast(params[0], '/'); String[] p2 = splitLast(params[1], '/'); - addField(p1[0], p1[1], - p2[0], p2[1]); + addField(p1[0], p1[1], p2[0], p2[1]); return null; - } - else if(line.startsWith("MD: ")) - { + } else if (line.startsWith("MD: ")) { String[] params = split4(line.substring(4), ' '); String[] p1 = splitLast(params[0], '/'); String[] p2 = splitLast(params[2], '/'); - addMethod(p1[0], p1[1], params[1], - p2[0], p2[1], params[3]); + addMethod(p1[0], p1[1], params[1], p2[0], p2[1], params[3]); return null; } return null; } }; - + ObfuscationRun.processLines(srgs, function); } - private void parseCSV(File csv) - { - log.out().println("Parsing "+csv.getName()); - - Function function = new Function() - { + private void parseCSV(File csv) { + log.out().println("Parsing " + csv.getName()); + + Function function = new Function() { + @Override - public Void apply(String line) - { - if(line.startsWith("func_") || line.startsWith("field_")) - { + public Void apply(String line) { + if (line.startsWith("func_") || line.startsWith("field_")) { int i = line.indexOf(','); String srg = line.substring(0, i); - int i2 = i+1; + int i2 = i + 1; i = line.indexOf(',', i2); String mcp = line.substring(i2, i); - + addMcpName(srg, mcp); } return null; } }; - + ObfuscationRun.processLines(csv, function); } } diff --git a/src/main/java/codechicken/obfuscator/ObfuscationRun.java b/src/main/java/codechicken/obfuscator/ObfuscationRun.java new file mode 100644 index 0000000..a1ae74f --- /dev/null +++ b/src/main/java/codechicken/obfuscator/ObfuscationRun.java @@ -0,0 +1,245 @@ +package codechicken.obfuscator; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.PrintStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.text.DecimalFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.tree.ClassNode; + +import com.google.common.base.Function; + +public class ObfuscationRun implements ILogStreams { + + public final ObfDirection obfDir; + public final ObfuscationMap obf; + public final ObfRemapper obfMapper; + public final ConstantObfuscator cstMappper; + + public File[] mappings; + public Map config; + + private PrintStream out = System.out; + private PrintStream err = System.err; + private PrintStream quietStream = new PrintStream(DummyOutputStream.instance); + + private boolean verbose; + private boolean quiet; + + public boolean clean; + private long startTime; + private boolean finished; + + public ObfuscationRun(boolean obfuscate, File[] mappings, Map config) { + obfDir = new ObfDirection().setObfuscate(obfuscate); + this.mappings = mappings; + this.config = config; + + obf = new ObfuscationMap().setLog(this); + obfMapper = new ObfRemapper(obf, obfDir); + cstMappper = new ConstantObfuscator( + obfMapper, + config.get("classConstantCalls").split(","), + config.get("descConstantCalls").split(",")); + } + + public ObfuscationRun setClean() { + clean = true; + return this; + } + + public ObfuscationRun setVerbose() { + verbose = true; + return this; + } + + public ObfuscationRun setQuiet() { + quiet = true; + return this; + } + + public ObfuscationRun setOut(PrintStream p) { + out = p; + return this; + } + + public PrintStream out() { + return quiet ? quietStream : out; + } + + public PrintStream fine() { + return verbose ? out : quietStream; + } + + public ObfuscationRun setErr(PrintStream p) { + err = p; + return this; + } + + public PrintStream err() { + return quiet ? quietStream : err; + } + + public ObfuscationRun setSearge() { + obfDir.setSearge(true); + return this; + } + + public ObfuscationRun setSeargeConstants() { + obfDir.setSeargeConstants(true); + return this; + } + + public void start() { + startTime = System.currentTimeMillis(); + } + + public static Map fillDefaults(Map config) { + if (!config.containsKey("excludedPackages")) config.put( + "excludedPackages", + "java/;sun/;javax/;scala/;" + "argo/;org/lwjgl/;org/objectweb/;org/bouncycastle/;com/google/"); + if (!config.containsKey("ignore")) config.put("ignore", "."); + if (!config.containsKey("classConstantCalls")) config.put( + "classConstantCalls", + "codechicken/lib/asm/ObfMapping.(Ljava/lang/String;)V," + + "codechicken/lib/asm/ObfMapping.subclass(Ljava/lang/String;)Lcodechicken/lib/asm/ObfMapping;," + + "codechicken/lib/asm/ObfMapping.(Lcodechicken/lib/asm/ObfMapping;Ljava/lang/String;)V"); + if (!config.containsKey("descConstantCalls")) config.put( + "descConstantCalls", + "codechicken/lib/asm/ObfMapping.(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/MethodVisitor.visitFieldInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/tree/MethodNode.visitFieldInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/MethodVisitor.visitMethodInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/tree/MethodNode.visitMethodInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/tree/MethodInsnNode.(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/tree/FieldInsnNode.(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + return config; + } + + public static void processLines(File file, Function function) { + try { + BufferedReader reader = new BufferedReader(new FileReader(file)); + String line; + while ((line = reader.readLine()) != null) function.apply(line); + reader.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void processFiles(File dir, Function function, boolean recursive) { + for (File file : dir.listFiles()) if (file.isDirectory() && recursive) processFiles(file, function, recursive); + else function.apply(file); + } + + public static void deleteDir(File directory, boolean remove) { + if (!directory.exists()) { + if (!remove) directory.mkdirs(); + return; + } + for (File file : directory.listFiles()) { + if (file.isDirectory()) { + deleteDir(file, true); + } else { + if (!file.delete()) throw new RuntimeException("Delete Failed: " + file); + } + } + if (remove) { + if (!directory.delete()) throw new RuntimeException("Delete Failed: " + directory); + } + } + + public static File[] parseConfDir(File confDir) { + File srgDir = new File(confDir, "conf"); + if (!srgDir.exists()) srgDir = confDir; + + File srgs = new File(srgDir, "packaged.srg"); + if (!srgs.exists()) srgs = new File(srgDir, "joined.srg"); + if (!srgs.exists()) throw new RuntimeException("Could not find packaged.srg or joined.srg"); + + File mapDir = new File(confDir, "mappings"); + if (!mapDir.exists()) mapDir = confDir; + + File methods = new File(mapDir, "methods.csv"); + if (!methods.exists()) throw new RuntimeException("Could not find methods.csv"); + File fields = new File(mapDir, "fields.csv"); + if (!fields.exists()) throw new RuntimeException("Could not find fields.csv"); + + return new File[] { srgs, methods, fields }; + } + + public long startTime() { + return startTime; + } + + public void remap(ClassNode cnode, ClassVisitor cv) { + cstMappper.transform(cnode); + cnode.accept(newRemapper(cv, obfMapper)); + } + + private static ClassVisitor newRemapper(ClassVisitor visitor, Remapper remapper) { + try { + return theConstructor.newInstance(visitor, remapper); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private static final Constructor theConstructor; + + static { + Class remapperClass; + try { + // noinspection unchecked + remapperClass = (Class) Class.forName("org.objectweb.asm.commons.ClassRemapper"); + } catch (ClassNotFoundException e) { + try { + // noinspection unchecked + remapperClass = (Class) Class + .forName("org.objectweb.asm.commons.RemappingClassAdapter"); + } catch (ClassNotFoundException ex) { + RuntimeException err = new RuntimeException(ex); + err.addSuppressed(e); + throw err; + } + } + try { + theConstructor = remapperClass.getConstructor(ClassVisitor.class, Remapper.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + public static List getParents(ClassNode cnode) { + List parents = new LinkedList(); + if (cnode.superName != null) parents.add(cnode.superName); + + for (String s_interface : cnode.interfaces) parents.add(s_interface); + + return parents; + } + + public void finish(boolean errored) { + long millis = System.currentTimeMillis() - startTime; + out().println( + (errored ? "Errored after" : "Done in ") + new DecimalFormat("0.00").format(millis / 1000D) + "s"); + finished = true; + } + + public boolean finished() { + return finished; + } + + public void parseMappings() { + obf.parseMappings(mappings); + } +} diff --git a/src/codechicken/obfuscator/SystemLogStreams.java b/src/main/java/codechicken/obfuscator/SystemLogStreams.java similarity index 62% rename from src/codechicken/obfuscator/SystemLogStreams.java rename to src/main/java/codechicken/obfuscator/SystemLogStreams.java index 1da29e2..bdf7a8b 100644 --- a/src/codechicken/obfuscator/SystemLogStreams.java +++ b/src/main/java/codechicken/obfuscator/SystemLogStreams.java @@ -2,19 +2,17 @@ import java.io.PrintStream; -public class SystemLogStreams implements ILogStreams -{ +public class SystemLogStreams implements ILogStreams { + public static SystemLogStreams inst = new SystemLogStreams(); - + @Override - public PrintStream err() - { + public PrintStream err() { return System.err; } - + @Override - public PrintStream out() - { + public PrintStream out() { return System.out; } } diff --git a/resources/assets/codechickencore/asm/tweaks.asm b/src/main/resources/assets/codechickencore/asm/tweaks.asm similarity index 100% rename from resources/assets/codechickencore/asm/tweaks.asm rename to src/main/resources/assets/codechickencore/asm/tweaks.asm diff --git a/resources/assets/codechickencore/lang/de_DE.lang b/src/main/resources/assets/codechickencore/lang/de_DE.lang similarity index 100% rename from resources/assets/codechickencore/lang/de_DE.lang rename to src/main/resources/assets/codechickencore/lang/de_DE.lang diff --git a/resources/assets/codechickencore/lang/en_US.lang b/src/main/resources/assets/codechickencore/lang/en_US.lang similarity index 100% rename from resources/assets/codechickencore/lang/en_US.lang rename to src/main/resources/assets/codechickencore/lang/en_US.lang diff --git a/resources/assets/codechickencore/lang/it_IT.lang b/src/main/resources/assets/codechickencore/lang/it_IT.lang similarity index 100% rename from resources/assets/codechickencore/lang/it_IT.lang rename to src/main/resources/assets/codechickencore/lang/it_IT.lang diff --git a/resources/assets/codechickencore/lang/ru_RU.lang b/src/main/resources/assets/codechickencore/lang/ru_RU.lang similarity index 100% rename from resources/assets/codechickencore/lang/ru_RU.lang rename to src/main/resources/assets/codechickencore/lang/ru_RU.lang diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info new file mode 100644 index 0000000..dbfad36 --- /dev/null +++ b/src/main/resources/mcmod.info @@ -0,0 +1,12 @@ +{ + "modListVersion": 2, + "modList": [{ + "modid": "${modId}", + "name": "${modName}", + "description": "Base common code for all chickenbones mods. Supporters: JBoyJr", + "version": "${modVersion}", + "mcversion": "${minecraftVersion}", + "url": "http://www.minecraftforum.net/topic/909223", + "authorList": ["ChickenBones", "GTNH Team"] + }] +}