diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..dfe077042
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..0ed9cfa05
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# eclipse
+bin
+*.launch
+.settings
+.metadata
+.classpath
+.project
+
+# idea
+out
+*.ipr
+*.iws
+*.iml
+/.idea
+
+# gradle
+build
+.gradle
+
+# other
+/run
+classes
+logs/debug.log
+logs/latest.log
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 000000000..0a041280b
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..c3709f967
--- /dev/null
+++ b/README.md
@@ -0,0 +1,74 @@
+# AE2 Fluid Crafting Rework
+
+Put fluids in the pattern!
+
+AE2 autocrafting is amazing and everyone loves it, but it is always becoming painful when dealing with fluids. You have to put fluids in a container or use a dummy item to write patterns with fluids.
+
+That's because AE2 doesn't support fluid as valid crafting ingredients before 1.18, so it can't handle fluids directly.
+
+However, it is changed now! With **AE2 Fluid Crafting** you can write patterns with fluids freely. Your AE system can output and accept fluids like items without worrying about how to handle these fluid cells.
+
+This is a rework and ported version of [ae2-fluid-crafting](https://github.com/phantamanta44/ae2-fluid-crafting)
+
+## Installation
+
+### 1.7.10
+Any version of AE2(Both Official AE2 and GTNH edition AE2 works).
+**Extra Cells isn't needed**
+
+### 1.12.2
+WIP
+
+### 1.16.5
+WIP
+
+## Basic Devices
+
+### Fluid Discretizer
+The **Fluid Discretizer** is a device that, when attached to ME network, exposes the contents of its fluid storage grid as items, which take the form of "fluid drops".
+It does this by functioning as a sort of storage bus: when fluid drops are removed from its storage via the item grid, it extracts the corresponding fluid from the fluid grid.
+Conversely, when fluid drops inserted into its storage via the item grid, it injects the corresponding fluid into the fluid grid.
+Each fluid drop is equivalent to one mB of its respective fluid, which means a full stack of them is equivalent to 64 mB.
+Fluid drops have an important property: when an ME interface attempts to export fluid drops to a machine, it will attempt to convert them to fluid.
+This means an interface exporting drops of gelid cryotheum into a fluid transposer will successfully fill the transposer's internal tank rather than inserting the drops as items.
+This is the central mechanic that makes fluid autocrafting possible.
+Note that the only way to convert between fluids and fluid drops is a discretizer attached to an ME network.
+While you could theoretically use this as a very convoluted method of transporting fluids, it is not recomomended to do so.
+
+### Fluid Pattern Encoder
+Most crafting recipes involving fluids require far more than 64 mB of a particular fluid, and so the standard pattern terminal will not do for encoding such recipes into patterns.
+This problem is solved by the **Fluid Pattern Encoder**, a utility that functions similarly to a pattern terminal.
+When a fluid-handling item (e.g. a bucket or tank) is inserted into the crafting ingredient slots, they will be converted into an equivalent stack of the corresponding fluid drops.
+Using this, patterns for recipes that require more than a stack of fluid drops can easily be encoded.
+AE2 Fluid Crafting also comes with a handy JEI integration module that allows the fluid pattern encoder to encode any JEI recipe involving fluids.
+This is the recommended way to play with the mod, since encoding patterns by hand is a little cumbersome.
+
+### Fluid Pattern Terminal
+
+Encoding recipes in a big and bulky workbench separate from the rest of your AE2 equipment can be a little inconvenient.
+Luckily, we have the **Fluid Pattern Terminal**, which combines the functionality of the standard pattern terminal and the fluid pattern encoder.
+Now, you can encode your fluid recipes using the same familiar interface you know and love!
+
+### Fluid Pattern Interface
+
+The standard ME interface lets you emit items and fluid packets with AE2FC, but it will only accept items, as in vanilla AE2.
+This is a little inconvenient when you want to build compact setups for autocrafting with fluid outputs, where you would need to use both an item interface for inputs, and a separate fluid interface for outputs.
+To make things easier, we have the **Fluid Pattern Interface**, which functions as a combination of an item interface and a fluid interface!
+Its GUI is the same as normal ME interface, but it can emit fluids directly instead of fluid packets! It also accepts fluid and item inputs.
+Automating fluid crafting machines has never been this quick and painless!
+
+### Fluid Packets
+
+When putting fluid pattern in a normal ME interface, it will emit fluid packets as fluid.
+So if you prefer to deal with item instead of fluids, you can transport these packets and turn them back to fluid with **ME Fluid Packet Decoder**.
+Simply connect the decoder to your ME network and insert the fluid packet; the decoder will, if possible, inject the fluid into your fluid storage grid.
+
+## Credited Works
+
+E. Geng(@phantamanta44) and KilaBash (@Yefancy) - Their amazing origin work in 1.12.
+
+## To-Do
+
+ - [ ] Port to 1.12, with supporting for PAE2
+
+ - [ ] Port to 1.16.5
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..cf7d305bd
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,78 @@
+buildscript {
+ repositories {
+ maven {
+ url 'https://maven.minecraftforge.net/'
+ }
+ maven {
+ name 'sonatype'
+ url 'https://oss.sonatype.org/content/repositories/snapshots/'
+ }
+ maven {
+ name 'Scala CI dependencies'
+ url 'https://repo1.maven.org/maven2/'
+ }
+ maven {
+ name 'jitpack'
+ url 'https://jitpack.io'
+ }
+ mavenLocal()
+ }
+ dependencies {
+ classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT'
+ }
+}
+
+apply plugin: 'net.minecraftforge.gradle.forge'
+apply plugin: 'idea'
+
+version = "1.0.0"
+group = "ae2fc"
+archivesBaseName = "Fluid Craft for AE2"
+
+sourceCompatibility = targetCompatibility = '1.8'
+compileJava {
+ sourceCompatibility = targetCompatibility = '1.8'
+}
+
+repositories {
+ maven { url 'https://dvs1.progwml6.com/files/maven/' }
+ maven { url 'https://jitpack.io/' }
+ maven { url 'https://cfa2.cursemaven.com/' }
+}
+
+dependencies {
+ compile 'com.github.phantamanta44:jsr305:1.0.1'
+ deobfCompile 'mezz.jei:jei_1.12.2:4.15.0.293'
+ deobfCompile "curse.maven:ae2-extended-life-570458:2747063" // pae2
+ deobfCompile "curse.maven:packagedauto-308380:2977147" // 1.0.3.14
+ deobfCompile "curse.maven:modular-machinery-270790:2761302" // 1.11.1
+}
+
+minecraft {
+ version = "1.12.2-14.23.5.2847"
+ runDir = "run"
+ mappings = "stable_39"
+ useDepAts = true
+ makeObfSourceJar = false
+}
+
+jar {
+ manifest {
+ attributes 'FMLCorePluginContainsFMLMod': 'true'
+ }
+}
+
+processResources {
+ inputs.property "version", project.version
+ inputs.property "mcversion", project.minecraft.version
+
+ from(sourceSets.main.resources.srcDirs) {
+ include 'mcmod.info'
+
+ expand 'version': project.version, 'mcversion': project.minecraft.version
+ }
+
+ from(sourceSets.main.resources.srcDirs) {
+ exclude 'mcmod.info'
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..736fb7d3f
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 000000000..2d146a2b8
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun May 24 18:34:45 CEST 2020
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100644
index 000000000..cccdd3d51
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# 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\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# 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
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ 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"
+ 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" -a "$nonstop" = "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"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # 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
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 000000000..f9553162f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@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=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_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=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/logo.png b/logo.png
new file mode 100644
index 000000000..129cbc24f
Binary files /dev/null and b/logo.png differ
diff --git a/src/main/java/com/glodblock/github/FluidCraft.java b/src/main/java/com/glodblock/github/FluidCraft.java
new file mode 100644
index 000000000..a751dbc21
--- /dev/null
+++ b/src/main/java/com/glodblock/github/FluidCraft.java
@@ -0,0 +1,44 @@
+package com.glodblock.github;
+
+import com.glodblock.github.proxy.CommonProxy;
+import com.glodblock.github.util.ModAndClassUtil;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.common.SidedProxy;
+import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
+
+@Mod(modid = FluidCraft.MODID, version = FluidCraft.VERSION, useMetadata = true)
+public class FluidCraft {
+
+ public static final String MODID = "ae2fc";
+ public static final String VERSION = "2.0.0-r";
+
+ @Mod.Instance(MODID)
+ public static FluidCraft INSTANCE;
+
+ @SidedProxy(clientSide = "com.glodblock.github.proxy.ClientProxy", serverSide = "com.glodblock.github.proxy.CommonProxy")
+ public static CommonProxy proxy;
+
+ @Mod.EventHandler
+ public void onPreInit(FMLPreInitializationEvent event) {
+ ModAndClassUtil.init();
+ proxy.preInit(event);
+ }
+
+ @Mod.EventHandler
+ public void onInit(FMLInitializationEvent event) {
+ proxy.init(event);
+ }
+
+ @Mod.EventHandler
+ public void onPostInit(FMLPostInitializationEvent event) {
+ proxy.postInit(event);
+ }
+
+ public static ResourceLocation resource(String path) {
+ return new ResourceLocation(MODID, path);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/GuiBurette.java b/src/main/java/com/glodblock/github/client/GuiBurette.java
new file mode 100644
index 000000000..575cf102c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiBurette.java
@@ -0,0 +1,119 @@
+package com.glodblock.github.client;
+
+import appeng.client.gui.AEBaseGui;
+import appeng.client.gui.widgets.GuiNumberBox;
+import appeng.core.localization.GuiText;
+import appeng.fluids.util.IAEFluidTank;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.container.ContainerBurette;
+import com.glodblock.github.client.render.FluidRenderUtils;
+import com.glodblock.github.common.tile.TileBurette;
+import com.glodblock.github.handler.ButtonMouseHandler;
+import com.glodblock.github.handler.TankMouseHandler;
+import com.glodblock.github.network.CPacketTransposeFluid;
+import com.glodblock.github.util.MouseRegionManager;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.util.ResourceLocation;
+
+import java.io.IOException;
+
+public class GuiBurette extends AEBaseGui {
+
+ private static final ResourceLocation TEX_BG = FluidCraft.resource("textures/gui/burette.png");
+ private static final int TANK_X = 52, TANK_Y = 17;
+ private static final int TANK_WIDTH = 16, TANK_HEIGHT = 32;
+
+ private final ContainerBurette cont;
+ private GuiNumberBox amountField;
+ private final MouseRegionManager mouseRegions = new MouseRegionManager(this);
+
+ public GuiBurette(InventoryPlayer ipl, TileBurette tile) {
+ super(new ContainerBurette(ipl, tile));
+ this.cont = (ContainerBurette)inventorySlots;
+
+ mouseRegions.addRegion(TANK_X, TANK_Y, TANK_WIDTH, TANK_HEIGHT,
+ new TankMouseHandler(cont.getTile().getFluidInventory(), 0));
+ mouseRegions.addRegion(70, 16, 7, 7, ButtonMouseHandler.dumpTank(cont, 0));
+ mouseRegions.addRegion(73, 42, 8, 16, createTransposeButton(true));
+ mouseRegions.addRegion(81, 42, 8, 16, createTransposeButton(false));
+ }
+
+ @Override
+ public void initGui() {
+ super.initGui();
+ amountField = new GuiNumberBox(fontRenderer, guiLeft + 95, guiTop + 46, 28, fontRenderer.FONT_HEIGHT, Integer.class);
+ amountField.setEnableBackgroundDrawing(false);
+ amountField.setMaxStringLength(4);
+ amountField.setTextColor(0xffffff);
+ amountField.setVisible(true);
+ amountField.setFocused(true);
+ amountField.setText("1000");
+ }
+
+ private ButtonMouseHandler createTransposeButton(boolean into) {
+ return new ButtonMouseHandler(into ? NameConst.TT_TRANSPOSE_IN : NameConst.TT_TRANSPOSE_OUT, () -> {
+ try {
+ int amount = Integer.parseInt(amountField.getText());
+ if (cont.canTranferFluid(into)) {
+ FluidCraft.proxy.netHandler.sendToServer(new CPacketTransposeFluid(amount, into));
+ }
+ } catch (NumberFormatException e) {
+ // NO-OP
+ }
+ });
+ }
+
+ @Override
+ protected void mouseClicked(int xCoord, int yCoord, int btn) throws IOException {
+ if (mouseRegions.onClick(xCoord, yCoord, btn)) {
+ amountField.mouseClicked(xCoord, yCoord, btn);
+ super.mouseClicked(xCoord, yCoord, btn);
+ }
+ }
+
+ @Override
+ protected void keyTyped(final char character, final int key) throws IOException {
+ if (!checkHotbarKeys(key)) {
+ if ((key == 211 || key == 205 || key == 203 || key == 14 || Character.isDigit(character))
+ && amountField.textboxKeyTyped(character, key)) {
+ try {
+ int amount = Integer.parseInt(amountField.getText());
+ if (amount < 0) {
+ amountField.setText("1");
+ } else {
+ amountField.setText(Long.toString(amount));
+ }
+ } catch (final NumberFormatException e) {
+ // NO-OP
+ }
+ } else {
+ super.keyTyped(character, key);
+ }
+ }
+ }
+
+ @Override
+ public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) {
+ mc.getTextureManager().bindTexture(TEX_BG);
+ drawTexturedModalRect(offsetX, offsetY, 0, 0, 176, ySize);
+ amountField.drawTextBox();
+ GlStateManager.color(1F, 1F, 1F, 1F);
+ }
+
+ @Override
+ public void drawFG(int offsetX, int offsetY, int mouseX, int mouseY) {
+ fontRenderer.drawString(getGuiDisplayName(I18n.format(NameConst.GUI_BURETTE)), 8, 6, 0x404040);
+ fontRenderer.drawString(GuiText.inventory.getLocal(), 8, ySize - 94, 0x404040);
+ GlStateManager.color(1F, 1F, 1F, 1F);
+
+ IAEFluidTank fluidInv = cont.getTile().getFluidInventory();
+ FluidRenderUtils.renderFluidIntoGuiCleanly(TANK_X, TANK_Y, TANK_WIDTH, TANK_HEIGHT,
+ fluidInv.getFluidInSlot(0), fluidInv.getTankProperties()[0].getCapacity());
+
+ mouseRegions.render(mouseX, mouseY);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/GuiFCPriority.java b/src/main/java/com/glodblock/github/client/GuiFCPriority.java
new file mode 100644
index 000000000..552a4bf5b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiFCPriority.java
@@ -0,0 +1,43 @@
+package com.glodblock.github.client;
+
+import appeng.client.gui.implementations.GuiPriority;
+import appeng.client.gui.widgets.GuiTabButton;
+import com.glodblock.github.interfaces.FCPriorityHost;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import com.glodblock.github.util.Ae2ReflectClient;
+import net.minecraft.client.gui.GuiButton;
+import net.minecraft.entity.player.InventoryPlayer;
+
+import java.io.IOException;
+
+public class GuiFCPriority extends GuiPriority {
+
+ private final GuiType originalGui;
+ private GuiTabButton originalGuiBtn;
+
+ public GuiFCPriority(final InventoryPlayer inventoryPlayer, final FCPriorityHost te) {
+ super(inventoryPlayer, te);
+ this.originalGui = te.getGuiType();
+ }
+
+ @Override
+ public void initGui() {
+ super.initGui();
+ originalGuiBtn = Ae2ReflectClient.getOriginalGuiButton(this);
+ }
+
+ @Override
+ protected void actionPerformed(final GuiButton btn) throws IOException {
+ if (btn == originalGuiBtn) {
+ InventoryHandler.switchGui(originalGui);
+ } else {
+ super.actionPerformed(btn);
+ }
+ }
+
+ protected String getBackground() {
+ return "guis/priority.png";
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/GuiFluidDualInterface.java b/src/main/java/com/glodblock/github/client/GuiFluidDualInterface.java
new file mode 100644
index 000000000..aaab99da8
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiFluidDualInterface.java
@@ -0,0 +1,50 @@
+package com.glodblock.github.client;
+
+import appeng.api.AEApi;
+import appeng.client.gui.widgets.GuiTabButton;
+import appeng.fluids.client.gui.GuiFluidInterface;
+import appeng.fluids.helper.IFluidInterfaceHost;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import com.glodblock.github.util.Ae2ReflectClient;
+import net.minecraft.client.gui.GuiButton;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.item.ItemStack;
+
+import java.io.IOException;
+
+public class GuiFluidDualInterface extends GuiFluidInterface {
+
+ private GuiTabButton switchInterface;
+ private GuiTabButton priorityBtn;
+
+ public GuiFluidDualInterface(final InventoryPlayer ip, final IFluidInterfaceHost te) {
+ super(ip, te);
+ }
+
+ @Override
+ public void initGui() {
+ super.initGui();
+ ItemStack icon = AEApi.instance().definitions().blocks().iface().maybeStack(1).orElse(ItemStack.EMPTY);
+ switchInterface = new GuiTabButton(guiLeft + 133, guiTop, icon, icon.getDisplayName(), itemRender);
+ buttonList.add(switchInterface);
+ priorityBtn = Ae2ReflectClient.getPriorityButton(this);
+ }
+
+ @Override
+ protected void actionPerformed(final GuiButton btn) throws IOException {
+ if (btn == switchInterface) {
+ InventoryHandler.switchGui(GuiType.DUAL_ITEM_INTERFACE);
+ } else if (btn == priorityBtn) {
+ InventoryHandler.switchGui(GuiType.PRIORITY);
+ } else {
+ super.actionPerformed(btn);
+ }
+ }
+
+ @Override
+ protected boolean drawUpgrades() {
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/GuiFluidPacketDecoder.java b/src/main/java/com/glodblock/github/client/GuiFluidPacketDecoder.java
new file mode 100644
index 000000000..0dd00f871
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiFluidPacketDecoder.java
@@ -0,0 +1,33 @@
+package com.glodblock.github.client;
+
+import appeng.client.gui.AEBaseGui;
+import appeng.core.localization.GuiText;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.container.ContainerFluidPacketDecoder;
+import com.glodblock.github.common.tile.TileFluidPacketDecoder;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.util.ResourceLocation;
+
+public class GuiFluidPacketDecoder extends AEBaseGui {
+
+ private static final ResourceLocation TEX_BG = FluidCraft.resource("textures/gui/fluid_packet_decoder.png");
+
+ public GuiFluidPacketDecoder(InventoryPlayer ipl, TileFluidPacketDecoder tile) {
+ super(new ContainerFluidPacketDecoder(ipl, tile));
+ }
+
+ @Override
+ public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) {
+ mc.getTextureManager().bindTexture(TEX_BG);
+ drawTexturedModalRect(offsetX, offsetY, 0, 0, 176, ySize);
+ }
+
+ @Override
+ public void drawFG(int offsetX, int offsetY, int mouseX, int mouseY) {
+ fontRenderer.drawString(getGuiDisplayName(I18n.format(NameConst.GUI_FLUID_PACKET_DECODER)), 8, 6, 0x404040);
+ fontRenderer.drawString(GuiText.inventory.getLocal(), 8, ySize - 94, 0x404040);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminal.java b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminal.java
new file mode 100644
index 000000000..1bac12d21
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminal.java
@@ -0,0 +1,53 @@
+package com.glodblock.github.client;
+
+import appeng.api.storage.ITerminalHost;
+import appeng.client.gui.implementations.GuiPatternTerm;
+import appeng.client.gui.widgets.GuiTabButton;
+import appeng.client.render.StackSizeRenderer;
+import appeng.container.slot.SlotFake;
+import com.glodblock.github.client.container.ContainerFluidPatternTerminal;
+import com.glodblock.github.client.render.FluidRenderUtils;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import com.glodblock.github.util.Ae2ReflectClient;
+import net.minecraft.client.gui.GuiButton;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.inventory.Slot;
+
+public class GuiFluidPatternTerminal extends GuiPatternTerm {
+
+ private final StackSizeRenderer stackSizeRenderer = Ae2ReflectClient.getStackSizeRenderer(this);
+ private GuiTabButton craftingStatusBtn;
+
+ public GuiFluidPatternTerminal(InventoryPlayer inventoryPlayer, ITerminalHost te) {
+ super(inventoryPlayer, te);
+ ContainerFluidPatternTerminal container = new ContainerFluidPatternTerminal(inventoryPlayer, te);
+ container.setGui(this);
+ this.inventorySlots = container;
+ Ae2ReflectClient.setGuiContainer(this, container);
+ }
+
+ @Override
+ public void initGui() {
+ super.initGui();
+ craftingStatusBtn = Ae2ReflectClient.getCraftingStatusButton(this);
+ }
+
+ @Override
+ public void drawSlot(Slot slot) {
+ if (!(slot instanceof SlotFake && FluidRenderUtils.renderFluidPacketIntoGuiSlot(
+ slot, slot.getStack(), stackSizeRenderer, fontRenderer))) {
+ super.drawSlot(slot);
+ }
+ }
+
+ @Override
+ protected void actionPerformed(final GuiButton btn) {
+ if (btn == craftingStatusBtn) {
+ InventoryHandler.switchGui(GuiType.FLUID_PAT_TERM_CRAFTING_STATUS);
+ } else {
+ super.actionPerformed(btn);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminalCraftingStatus.java b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminalCraftingStatus.java
new file mode 100644
index 000000000..fec68de3a
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminalCraftingStatus.java
@@ -0,0 +1,37 @@
+package com.glodblock.github.client;
+
+import appeng.api.storage.ITerminalHost;
+import appeng.client.gui.implementations.GuiCraftingStatus;
+import appeng.client.gui.widgets.GuiTabButton;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import com.glodblock.github.util.Ae2ReflectClient;
+import net.minecraft.client.gui.GuiButton;
+import net.minecraft.entity.player.InventoryPlayer;
+
+import java.io.IOException;
+
+public class GuiFluidPatternTerminalCraftingStatus extends GuiCraftingStatus {
+
+ private GuiTabButton originalGuiBtn;
+
+ public GuiFluidPatternTerminalCraftingStatus(InventoryPlayer inventoryPlayer, ITerminalHost te) {
+ super(inventoryPlayer, te);
+ }
+
+ @Override
+ public void initGui() {
+ super.initGui();
+ originalGuiBtn = Ae2ReflectClient.getOriginalGuiButton(this);
+ }
+
+ @Override
+ protected void actionPerformed(final GuiButton btn) throws IOException {
+ if (btn == originalGuiBtn) {
+ InventoryHandler.switchGui(GuiType.FLUID_PATTERN_TERMINAL);
+ } else {
+ super.actionPerformed(btn);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/GuiIngredientBuffer.java b/src/main/java/com/glodblock/github/client/GuiIngredientBuffer.java
new file mode 100644
index 000000000..c41816192
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiIngredientBuffer.java
@@ -0,0 +1,77 @@
+package com.glodblock.github.client;
+
+import appeng.client.gui.AEBaseGui;
+import appeng.core.localization.GuiText;
+import appeng.fluids.util.IAEFluidTank;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.container.ContainerIngredientBuffer;
+import com.glodblock.github.client.render.FluidRenderUtils;
+import com.glodblock.github.common.tile.TileIngredientBuffer;
+import com.glodblock.github.handler.ButtonMouseHandler;
+import com.glodblock.github.handler.TankMouseHandler;
+import com.glodblock.github.util.MouseRegionManager;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.util.ResourceLocation;
+
+import java.io.IOException;
+
+public class GuiIngredientBuffer extends AEBaseGui {
+
+ private static final ResourceLocation TEX_BG = FluidCraft.resource("textures/gui/ingredient_buffer.png");
+ private static final int TANK_X = 47, TANK_X_OFF = 22, TANK_Y = 18;
+ private static final int TANK_WIDTH = 16, TANK_HEIGHT = 74;
+
+ private final ContainerIngredientBuffer cont;
+ private final MouseRegionManager mouseRegions = new MouseRegionManager(this);
+
+ public GuiIngredientBuffer(InventoryPlayer ipl, TileIngredientBuffer tile) {
+ super(new ContainerIngredientBuffer(ipl, tile));
+ this.cont = (ContainerIngredientBuffer)inventorySlots;
+ this.ySize = 222;
+ for (int i = 0; i < 4; i++) {
+ mouseRegions.addRegion(TANK_X + TANK_X_OFF * i, TANK_Y, TANK_WIDTH, TANK_HEIGHT,
+ new TankMouseHandler(cont.getTile().getFluidInventory(), i));
+ mouseRegions.addRegion(TANK_X + 10 + 22 * i, TANK_Y + TANK_HEIGHT + 2, 7, 7,
+ ButtonMouseHandler.dumpTank(cont, i));
+ }
+ }
+
+ @Override
+ protected void mouseClicked(int xCoord, int yCoord, int btn) throws IOException {
+ if (mouseRegions.onClick(xCoord, yCoord, btn)) {
+ super.mouseClicked(xCoord, yCoord, btn);
+ }
+ }
+
+ @Override
+ public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) {
+ mc.getTextureManager().bindTexture(TEX_BG);
+ drawTexturedModalRect(offsetX, offsetY, 0, 0, 176, ySize);
+ }
+
+ @Override
+ public void drawFG(int offsetX, int offsetY, int mouseX, int mouseY) {
+ fontRenderer.drawString(getGuiDisplayName(I18n.format(NameConst.GUI_INGREDIENT_BUFFER)), 8, 6, 0x404040);
+ fontRenderer.drawString(GuiText.inventory.getLocal(), 8, ySize - 94, 0x404040);
+ GlStateManager.color(1F, 1F, 1F, 1F);
+
+ IAEFluidTank fluidInv = cont.getTile().getFluidInventory();
+ mc.getTextureManager().bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
+ Tessellator tess = Tessellator.getInstance();
+ BufferBuilder buf = tess.getBuffer();
+ for (int i = 0; i < 4; i++) {
+ FluidRenderUtils.renderFluidIntoGui(tess, buf, TANK_X + i * TANK_X_OFF, TANK_Y, TANK_WIDTH, TANK_HEIGHT,
+ fluidInv.getFluidInSlot(i), fluidInv.getTankProperties()[i].getCapacity());
+ }
+ GlStateManager.color(1F, 1F, 1F, 1F);
+
+ mouseRegions.render(mouseX, mouseY);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/GuiItemDualInterface.java b/src/main/java/com/glodblock/github/client/GuiItemDualInterface.java
new file mode 100644
index 000000000..60dde6ed2
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiItemDualInterface.java
@@ -0,0 +1,50 @@
+package com.glodblock.github.client;
+
+import appeng.api.AEApi;
+import appeng.client.gui.implementations.GuiInterface;
+import appeng.client.gui.widgets.GuiTabButton;
+import appeng.helpers.IInterfaceHost;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import com.glodblock.github.util.Ae2ReflectClient;
+import net.minecraft.client.gui.GuiButton;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.item.ItemStack;
+
+import java.io.IOException;
+
+public class GuiItemDualInterface extends GuiInterface {
+
+ private GuiTabButton switchInterface;
+ private GuiTabButton priorityBtn;
+
+ public GuiItemDualInterface(final InventoryPlayer inventoryPlayer, final IInterfaceHost te) {
+ super(inventoryPlayer, te);
+ }
+
+ @Override
+ protected void addButtons() {
+ super.addButtons();
+ ItemStack icon = AEApi.instance().definitions().blocks().fluidIface().maybeStack(1).orElse(ItemStack.EMPTY);
+ switchInterface = new GuiTabButton(guiLeft + 133, guiTop, icon, icon.getDisplayName(), itemRender);
+ buttonList.add(switchInterface);
+ priorityBtn = Ae2ReflectClient.getPriorityButton(this);
+ }
+
+ @Override
+ protected String getBackground() {
+ return "guis/interface.png";
+ }
+
+ @Override
+ protected void actionPerformed(final GuiButton btn) throws IOException {
+ if (btn == switchInterface) {
+ InventoryHandler.switchGui(GuiType.DUAL_FLUID_INTERFACE);
+ } else if (btn == priorityBtn) {
+ InventoryHandler.switchGui(GuiType.PRIORITY);
+ } else {
+ super.actionPerformed(btn);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/container/ContainerBurette.java b/src/main/java/com/glodblock/github/client/container/ContainerBurette.java
new file mode 100644
index 000000000..6a1bf5a7e
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerBurette.java
@@ -0,0 +1,134 @@
+package com.glodblock.github.client.container;
+
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.container.AEBaseContainer;
+import appeng.container.slot.SlotNormal;
+import appeng.fluids.container.IFluidSyncContainer;
+import appeng.fluids.helper.FluidSyncHelper;
+import appeng.fluids.util.IAEFluidTank;
+import appeng.util.Platform;
+import com.glodblock.github.common.tile.TileBurette;
+import com.glodblock.github.interfaces.TankDumpable;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.inventory.IContainerListener;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
+import net.minecraftforge.fluids.capability.IFluidHandler;
+import net.minecraftforge.fluids.capability.IFluidHandlerItem;
+import net.minecraftforge.fluids.capability.IFluidTankProperties;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+
+public class ContainerBurette extends AEBaseContainer implements IFluidSyncContainer, TankDumpable {
+
+ private final TileBurette tile;
+ private final FluidSyncHelper fluidSync;
+
+ public ContainerBurette(InventoryPlayer ipl, TileBurette tile) {
+ super(ipl, tile);
+ this.tile = tile;
+ this.fluidSync = new FluidSyncHelper(tile.getFluidInventory(), 0);
+ addSlotToContainer(new SlotNormal(tile.getInternalInventory(), 0, 52, 53));
+ bindPlayerInventory(ipl, 0, 84);
+ }
+
+ public TileBurette getTile() {
+ return tile;
+ }
+
+ public boolean canTranferFluid(boolean into) {
+ IAEFluidTank tileTank = tile.getFluidInventory();
+ IFluidTankProperties tileTankInfo = tileTank.getTankProperties()[0];
+ IAEFluidStack tileFluid = tileTank.getFluidInSlot(0);
+ if (into) {
+ if (tileFluid != null && tileFluid.getStackSize() >= tileTankInfo.getCapacity()) {
+ return false;
+ }
+ } else if (tileFluid == null) {
+ return false;
+ }
+ ItemStack stack = tile.getInternalInventory().getStackInSlot(0);
+ if (stack.isEmpty() || !stack.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null)) {
+ return false;
+ }
+ IFluidHandlerItem itemTank = Objects.requireNonNull(
+ stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null));
+ for (IFluidTankProperties itemTankInfo : itemTank.getTankProperties()) {
+ if (into) {
+ if (itemTankInfo.canDrain() && tileTankInfo.canFillFluidType(itemTankInfo.getContents())) {
+ return true;
+ }
+ } else if (itemTankInfo.canFillFluidType(tileFluid.getFluidStack())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void tryTransferFluid(int amount, boolean into) {
+ ItemStack stack = tile.getInternalInventory().getStackInSlot(0);
+ if (stack.isEmpty() || !stack.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null)) {
+ return;
+ }
+ IAEFluidTank tileTank = tile.getFluidInventory();
+ IFluidHandlerItem itemTank = Objects.requireNonNull(
+ stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null));
+ if (into) {
+ transferFluidBetween(itemTank, tileTank, amount);
+ } else {
+ transferFluidBetween(tileTank, itemTank, amount);
+ }
+ tile.getInternalInventory().setStackInSlot(0, itemTank.getContainer());
+ }
+
+ private void transferFluidBetween(IFluidHandler from, IFluidHandler to, int amount) {
+ // simulated pass to figure out the real amount we can transfer
+ FluidStack fluid = from.drain(amount, false);
+ if (fluid == null) {
+ return;
+ }
+ amount = Math.min(amount, to.fill(fluid, false));
+ // actually do the transfer
+ fluid = from.drain(amount, true);
+ if (fluid != null) {
+ fluid.amount -= to.fill(fluid, true);
+ if (fluid.amount > 0) { // just in case the tanks don't behave exactly as simulated
+ from.fill(fluid, true);
+ }
+ }
+ }
+
+ @Override
+ public void detectAndSendChanges() {
+ super.detectAndSendChanges();
+ if (Platform.isServer()) {
+ fluidSync.sendDiff(listeners);
+ }
+ }
+
+ @Override
+ public void addListener(@Nonnull IContainerListener listener) {
+ super.addListener(listener);
+ fluidSync.sendFull(Collections.singleton(listener));
+ }
+
+ @Override
+ public void receiveFluidSlots(Map fluids) {
+ fluidSync.readPacket(fluids);
+ }
+
+ @Override
+ public boolean canDumpTank(int index) {
+ return tile.getFluidInventory().getFluidInSlot(0) != null;
+ }
+
+ @Override
+ public void dumpTank(int index) {
+ tile.getFluidInventory().setFluidInSlot(0, null);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFluidPacketDecoder.java b/src/main/java/com/glodblock/github/client/container/ContainerFluidPacketDecoder.java
new file mode 100644
index 000000000..220f8b06a
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPacketDecoder.java
@@ -0,0 +1,16 @@
+package com.glodblock.github.client.container;
+
+import appeng.container.AEBaseContainer;
+import appeng.container.slot.SlotNormal;
+import com.glodblock.github.common.tile.TileFluidPacketDecoder;
+import net.minecraft.entity.player.InventoryPlayer;
+
+public class ContainerFluidPacketDecoder extends AEBaseContainer {
+
+ public ContainerFluidPacketDecoder(InventoryPlayer ipl, TileFluidPacketDecoder tile) {
+ super(ipl, tile);
+ addSlotToContainer(new SlotNormal(tile.getInventory(), 0, 80, 35));
+ bindPlayerInventory(ipl, 0, 84);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternEncoder.java b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternEncoder.java
new file mode 100644
index 000000000..139052825
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternEncoder.java
@@ -0,0 +1,216 @@
+package com.glodblock.github.client.container;
+
+import appeng.api.AEApi;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.container.AEBaseContainer;
+import appeng.container.slot.SlotFake;
+import appeng.container.slot.SlotRestrictedInput;
+import appeng.helpers.InventoryAction;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidEncodedPattern;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.common.tile.TileFluidPatternEncoder;
+import com.glodblock.github.handler.AeItemStackHandler;
+import com.glodblock.github.interfaces.AeStackInventory;
+import com.glodblock.github.interfaces.PatternConsumer;
+import com.glodblock.github.interfaces.SlotFluid;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.util.FluidPatternDetails;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
+import net.minecraftforge.fluids.capability.IFluidTankProperties;
+import net.minecraftforge.items.ItemHandlerHelper;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class ContainerFluidPatternEncoder extends AEBaseContainer implements PatternConsumer {
+
+ private final TileFluidPatternEncoder tile;
+
+ public ContainerFluidPatternEncoder(InventoryPlayer ipl, TileFluidPatternEncoder tile) {
+ super(ipl, tile);
+ this.tile = tile;
+ AeItemStackHandler crafting = new AeItemStackHandler(tile.getCraftingSlots());
+ AeItemStackHandler output = new AeItemStackHandler(tile.getOutputSlots());
+ for (int y = 0; y < 3; y++) {
+ for (int x = 0; x < 3; x++) {
+ addSlotToContainer(new SlotFluidConvertingFake(crafting, y * 3 + x, 23 + x * 18, 17 + y * 18));
+ }
+ addSlotToContainer(new SlotFluidConvertingFake(output, y, 113, 17 + y * 18));
+ }
+ addSlotToContainer(new SlotRestrictedInput(
+ SlotRestrictedInput.PlacableItemType.BLANK_PATTERN, tile.getInventory(), 0, 138, 20, ipl));
+ addSlotToContainer(new SlotRestrictedInput(
+ SlotRestrictedInput.PlacableItemType.ENCODED_PATTERN, tile.getInventory(), 1, 138, 50, ipl));
+ bindPlayerInventory(ipl, 0, 84);
+ }
+
+ public TileFluidPatternEncoder getTile() {
+ return tile;
+ }
+
+ public boolean canEncodePattern() {
+ if (isNotPattern(tile.getInventory().getStackInSlot(0)) && isNotPattern(tile.getInventory().getStackInSlot(1))) {
+ return false;
+ }
+ find_input:
+ {
+ for (IAEItemStack stack : tile.getCraftingSlots()) {
+ if (stack != null && stack.getStackSize() > 0) {
+ break find_input;
+ }
+ }
+ return false;
+ }
+ for (IAEItemStack stack : tile.getOutputSlots()) {
+ if (stack != null && stack.getStackSize() > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isNotPattern(ItemStack stack) {
+ return stack.isEmpty() || !(AEApi.instance().definitions().materials().blankPattern().isSameAs(stack)
+ || (stack.getItem() instanceof ItemFluidEncodedPattern));
+ }
+
+ public void encodePattern() {
+ if (canEncodePattern()) {
+ // if there is an encoded pattern, overwrite it; otherwise, consume a blank
+ if (tile.getInventory().getStackInSlot(1).isEmpty()) {
+ tile.getInventory().extractItem(0, 1, false); // this better work
+ }
+ ItemStack patternStack = new ItemStack(FCItems.DENSE_ENCODED_PATTERN);
+ FluidPatternDetails pattern = new FluidPatternDetails(patternStack);
+ pattern.setInputs(collectAeInventory(tile.getCraftingSlots()));
+ pattern.setOutputs(collectAeInventory(tile.getOutputSlots()));
+ tile.getInventory().setStackInSlot(1, pattern.writeToStack());
+ }
+ }
+
+ private static IAEItemStack[] collectAeInventory(AeStackInventory inv) {
+ // see note at top of DensePatternDetails
+ List acc = new ArrayList<>();
+ for (IAEItemStack stack : inv) {
+ if (stack != null) {
+ if (stack.getItem() instanceof ItemFluidPacket) {
+ IAEItemStack dropStack = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stack));
+ if (dropStack != null) {
+ acc.add(dropStack);
+ continue;
+ }
+ }
+ acc.add(stack);
+ }
+ }
+ return acc.toArray(new IAEItemStack[0]);
+ }
+
+ // adapted from ae2's AEBaseContainer#doAction
+ @Override
+ public void doAction(EntityPlayerMP player, InventoryAction action, int slotId, long id) {
+ Slot slot = getSlot(slotId);
+ if (slot instanceof SlotFluidConvertingFake) {
+ final ItemStack stack = player.inventory.getItemStack();
+ switch (action) {
+ case PICKUP_OR_SET_DOWN:
+ if (stack.isEmpty()) {
+ slot.putStack(ItemStack.EMPTY);
+ } else {
+ ((SlotFluidConvertingFake)slot).putConvertedStack(stack.copy());
+ }
+ break;
+ case PLACE_SINGLE:
+ if (!stack.isEmpty()) {
+ ((SlotFluidConvertingFake)slot).putConvertedStack(ItemHandlerHelper.copyStackWithSize(stack, 1));
+ }
+ break;
+ case SPLIT_OR_PLACE_SINGLE:
+ ItemStack inSlot = slot.getStack();
+ if (!inSlot.isEmpty()) {
+ if (stack.isEmpty()) {
+ slot.putStack(ItemHandlerHelper.copyStackWithSize(inSlot, Math.max(1, inSlot.getCount() - 1)));
+ } else if (stack.isItemEqual(inSlot)) {
+ slot.putStack(ItemHandlerHelper.copyStackWithSize(inSlot,
+ Math.min(inSlot.getMaxStackSize(), inSlot.getCount() + 1)));
+ } else {
+ ((SlotFluidConvertingFake)slot).putConvertedStack(ItemHandlerHelper.copyStackWithSize(stack, 1));
+ }
+ } else if (!stack.isEmpty()) {
+ ((SlotFluidConvertingFake)slot).putConvertedStack(ItemHandlerHelper.copyStackWithSize(stack, 1));
+ }
+ break;
+ }
+ } else {
+ super.doAction(player, action, slotId, id);
+ }
+ }
+
+ @Override
+ public void acceptPattern(IAEItemStack[] inputs, IAEItemStack[] outputs) {
+ copyStacks(inputs, tile.getCraftingSlots());
+ copyStacks(outputs, tile.getOutputSlots());
+ }
+
+ private static void copyStacks(IAEItemStack[] src, AeStackInventory dest) {
+ int bound = Math.min(src.length, dest.getSlotCount());
+ for (int i = 0; i < bound; i++) {
+ dest.setStack(i, src[i]);
+ }
+ }
+
+ private static class SlotFluidConvertingFake extends SlotFake implements SlotFluid {
+
+ private final AeStackInventory inv;
+
+ public SlotFluidConvertingFake(AeItemStackHandler inv, int idx, int x, int y) {
+ super(inv, idx, x, y);
+ this.inv = inv.getAeInventory();
+ }
+
+ @Override
+ public void putStack(ItemStack stack) {
+ inv.setStack(getSlotIndex(), AEItemStack.fromItemStack(stack));
+ }
+
+ @Override
+ public void setAeStack(@Nullable IAEItemStack stack, boolean sync) {
+ inv.setStack(getSlotIndex(), stack);
+ }
+
+ public void putConvertedStack(ItemStack stack) {
+ if (stack.isEmpty()) {
+ setAeStack(null, false);
+ return;
+ } else if (stack.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null)) {
+ IFluidTankProperties[] tanks = Objects.requireNonNull(
+ stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null))
+ .getTankProperties();
+ for (IFluidTankProperties tank : tanks) {
+ IAEItemStack aeStack = ItemFluidPacket.newAeStack(tank.getContents());
+ if (aeStack != null) {
+ setAeStack(aeStack, false);
+ return;
+ }
+ }
+ }
+ putStack(stack);
+ }
+
+ @Nullable
+ @Override
+ public IAEItemStack getAeStack() {
+ return inv.getStack(getSlotIndex());
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternTerminal.java b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternTerminal.java
new file mode 100644
index 000000000..306e3b006
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternTerminal.java
@@ -0,0 +1,148 @@
+package com.glodblock.github.client.container;
+
+import appeng.api.AEApi;
+import appeng.api.definitions.IDefinitions;
+import appeng.api.storage.ITerminalHost;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.container.implementations.ContainerPatternTerm;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidEncodedPattern;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.common.part.PartFluidPatternTerminal;
+import com.glodblock.github.interfaces.PatternConsumer;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.util.Ae2Reflect;
+import com.glodblock.github.util.FluidPatternDetails;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ContainerFluidPatternTerminal extends ContainerPatternTerm implements PatternConsumer {
+
+ private final Slot[] craftingSlots;
+ private final Slot[] outputSlots;
+ private final Slot patternSlotIN;
+ private final Slot patternSlotOUT;
+
+ public ContainerFluidPatternTerminal(InventoryPlayer ip, ITerminalHost monitorable) {
+ super(ip, monitorable);
+ craftingSlots = Ae2Reflect.getCraftingSlots(this);
+ outputSlots = Ae2Reflect.getOutputSlots(this);
+ patternSlotIN = Ae2Reflect.getPatternSlotIn(this);
+ patternSlotOUT = Ae2Reflect.getPatternSlotOut(this);
+ }
+
+ @Override
+ public void encode() {
+ if (!checkHasFluidPattern()) {
+ super.encode();
+ return;
+ }
+ ItemStack stack = this.patternSlotOUT.getStack();
+ if (stack.isEmpty()) {
+ stack = this.patternSlotIN.getStack();
+ if (stack.isEmpty() || !isPattern(stack)) {
+ return;
+ }
+ if (stack.getCount() == 1) {
+ this.patternSlotIN.putStack(ItemStack.EMPTY);
+ } else {
+ stack.shrink(1);
+ }
+ encodeFluidPattern();
+ } else if (isPattern(stack)) {
+ encodeFluidPattern();
+ }
+ }
+
+ private static boolean isPattern(final ItemStack output) {
+ if (output.isEmpty()) {
+ return false;
+ }
+ if (output.getItem() instanceof ItemFluidEncodedPattern) {
+ return true;
+ }
+ final IDefinitions defs = AEApi.instance().definitions();
+ return defs.items().encodedPattern().isSameAs(output) || defs.materials().blankPattern().isSameAs(output);
+ }
+
+ private boolean checkHasFluidPattern() {
+ if (this.craftingMode) {
+ return false;
+ }
+ boolean hasFluid = false, search = false;
+ for (Slot craftingSlot : this.craftingSlots) {
+ final ItemStack crafting = craftingSlot.getStack();
+ if (crafting.isEmpty()) {
+ continue;
+ }
+ search = true;
+ if (crafting.getItem() instanceof ItemFluidPacket) {
+ hasFluid = true;
+ break;
+ }
+ }
+ if (!search) { // search=false -> inputs were empty
+ return false;
+ }
+ // `search` should be true at this point
+ for (Slot outputSlot : this.outputSlots) {
+ final ItemStack out = outputSlot.getStack();
+ if (out.isEmpty()) {
+ continue;
+ }
+ search = false;
+ if (hasFluid) {
+ break;
+ } else if (out.getItem() instanceof ItemFluidPacket) {
+ hasFluid = true;
+ break;
+ }
+ }
+ return hasFluid && !search; // search=true -> outputs were empty
+ }
+
+ private void encodeFluidPattern() {
+ ItemStack patternStack = new ItemStack(FCItems.DENSE_ENCODED_PATTERN);
+ FluidPatternDetails pattern = new FluidPatternDetails(patternStack);
+ pattern.setInputs(collectInventory(craftingSlots));
+ pattern.setOutputs(collectInventory(outputSlots));
+ patternSlotOUT.putStack(pattern.writeToStack());
+ }
+
+ private static IAEItemStack[] collectInventory(Slot[] slots) {
+ // see note at top of DensePatternDetails
+ List acc = new ArrayList<>();
+ for (Slot slot : slots) {
+ ItemStack stack = slot.getStack();
+ if (stack.isEmpty()) {
+ continue;
+ }
+ if (stack.getItem() instanceof ItemFluidPacket) {
+ IAEItemStack dropStack = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stack));
+ if (dropStack != null) {
+ acc.add(dropStack);
+ continue;
+ }
+ }
+ IAEItemStack aeStack = AEItemStack.fromItemStack(stack);
+ if (aeStack == null) {
+ continue;
+ }
+ acc.add(aeStack);
+ }
+ return acc.toArray(new IAEItemStack[0]);
+ }
+
+ @Override
+ public void acceptPattern(IAEItemStack[] inputs, IAEItemStack[] outputs) {
+ if (getPatternTerminal() instanceof PartFluidPatternTerminal) {
+ ((PartFluidPatternTerminal)getPatternTerminal()).onChangeCrafting(inputs, outputs);
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/container/ContainerIngredientBuffer.java b/src/main/java/com/glodblock/github/client/container/ContainerIngredientBuffer.java
new file mode 100644
index 000000000..9cab2265f
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerIngredientBuffer.java
@@ -0,0 +1,40 @@
+package com.glodblock.github.client.container;
+
+import appeng.container.AEBaseContainer;
+import appeng.container.slot.SlotNormal;
+import com.glodblock.github.common.tile.TileIngredientBuffer;
+import com.glodblock.github.interfaces.TankDumpable;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraftforge.items.IItemHandler;
+
+public class ContainerIngredientBuffer extends AEBaseContainer implements TankDumpable {
+
+ private final TileIngredientBuffer tile;
+
+ public ContainerIngredientBuffer(InventoryPlayer ipl, TileIngredientBuffer tile) {
+ super(ipl, tile);
+ this.tile = tile;
+ IItemHandler inv = tile.getInternalInventory();
+ for (int i = 0; i < 9; i++) {
+ addSlotToContainer(new SlotNormal(inv, i, 8 + 18 * i, 108));
+ }
+ bindPlayerInventory(ipl, 0, 140);
+ }
+
+ public TileIngredientBuffer getTile() {
+ return tile;
+ }
+
+ @Override
+ public boolean canDumpTank(int index) {
+ return tile.getFluidInventory().getFluidInSlot(index) != null;
+ }
+
+ @Override
+ public void dumpTank(int index) {
+ if (index >= 0 && index < tile.getFluidInventory().getSlots()) {
+ tile.getFluidInventory().setFluidInSlot(index, null);
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/container/GuiFluidPatternEncoder.java b/src/main/java/com/glodblock/github/client/container/GuiFluidPatternEncoder.java
new file mode 100644
index 000000000..1ca5dfdef
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/GuiFluidPatternEncoder.java
@@ -0,0 +1,107 @@
+package com.glodblock.github.client.container;
+
+import appeng.api.storage.data.IAEItemStack;
+import appeng.client.gui.AEBaseGui;
+import appeng.client.render.StackSizeRenderer;
+import appeng.core.localization.GuiText;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.render.FluidRenderUtils;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.common.tile.TileFluidPatternEncoder;
+import com.glodblock.github.interfaces.SlotFluid;
+import com.glodblock.github.inventory.slot.SlotSingleItem;
+import com.glodblock.github.network.CPacketEncodePattern;
+import com.glodblock.github.util.Ae2ReflectClient;
+import com.glodblock.github.util.MouseRegionManager;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.InventoryPlayer;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.TextFormatting;
+import net.minecraftforge.fluids.FluidStack;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class GuiFluidPatternEncoder extends AEBaseGui {
+
+ private static final ResourceLocation TEX_BG = FluidCraft.resource("textures/gui/fluid_pattern_encoder.png");
+
+ private final ContainerFluidPatternEncoder cont;
+ private final MouseRegionManager mouseRegions = new MouseRegionManager(this);
+ private final StackSizeRenderer stackSizeRenderer = Ae2ReflectClient.getStackSizeRenderer(this);
+
+ public GuiFluidPatternEncoder(InventoryPlayer ipl, TileFluidPatternEncoder tile) {
+ super(new ContainerFluidPatternEncoder(ipl, tile));
+ this.cont = (ContainerFluidPatternEncoder)inventorySlots;
+ mouseRegions.addRegion(141, 38, 10, 10, new MouseRegionManager.Handler() {
+ @Override
+ public List getTooltip() {
+ return Collections.singletonList(I18n.format(NameConst.TT_ENCODE_PATTERN));
+ }
+
+ @Override
+ public boolean onClick(int button) {
+ if (button == 0) {
+ if (cont.canEncodePattern()) {
+ FluidCraft.proxy.netHandler.sendToServer(new CPacketEncodePattern());
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ @Override
+ protected void mouseClicked(int xCoord, int yCoord, int btn) throws IOException {
+ if (mouseRegions.onClick(xCoord, yCoord, btn)) {
+ super.mouseClicked(xCoord, yCoord, btn);
+ }
+ }
+
+ @Override
+ public void drawBG(int offsetX, int offsetY, int mouseX, int mouseY) {
+ mc.getTextureManager().bindTexture(TEX_BG);
+ drawTexturedModalRect(offsetX, offsetY, 0, 0, 176, ySize);
+ }
+
+ @Override
+ public void drawFG(int offsetX, int offsetY, int mouseX, int mouseY) {
+ fontRenderer.drawString(getGuiDisplayName(I18n.format(NameConst.GUI_FLUID_PATTERN_ENCODER)), 8, 6, 0x404040);
+ fontRenderer.drawString(GuiText.inventory.getLocal(), 8, ySize - 94, 0x404040);
+ mouseRegions.render(mouseX, mouseY);
+ }
+
+ @Override
+ public void drawSlot(Slot slot) {
+ if (slot instanceof SlotFluid) {
+ IAEItemStack stack = ((SlotFluid)slot).getAeStack();
+ if (FluidRenderUtils.renderFluidPacketIntoGuiSlot(slot, stack, stackSizeRenderer, fontRenderer)) {
+ return;
+ }
+ super.drawSlot(new SlotSingleItem(slot));
+ stackSizeRenderer.renderStackSize(fontRenderer, stack, slot.xPos, slot.yPos);
+ } else {
+ super.drawSlot(slot);
+ }
+ }
+
+ @Override
+ @Nonnull
+ public List getItemToolTip(ItemStack stack) {
+ if (stack.getItem() instanceof ItemFluidPacket) {
+ FluidStack fluid = ItemFluidPacket.getFluidStack(stack);
+ if (fluid != null) {
+ return Arrays.asList(fluid.getLocalizedName(), String.format(TextFormatting.GRAY + "%,d mB", fluid.amount));
+ }
+ }
+ return super.getItemToolTip(stack);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/model/DenseEncodedPatternModel.java b/src/main/java/com/glodblock/github/client/model/DenseEncodedPatternModel.java
new file mode 100644
index 000000000..7be8e3e8c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/model/DenseEncodedPatternModel.java
@@ -0,0 +1,67 @@
+package com.glodblock.github.client.model;
+
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.util.Ae2ReflectClient;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.client.renderer.block.model.IBakedModel;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.vertex.VertexFormat;
+import net.minecraft.client.resources.IResourceManager;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.client.model.ICustomModelLoader;
+import net.minecraftforge.client.model.IModel;
+import net.minecraftforge.client.model.ModelLoaderRegistry;
+import net.minecraftforge.client.model.PerspectiveMapWrapper;
+import net.minecraftforge.common.model.IModelState;
+
+import javax.annotation.Nonnull;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.function.Function;
+
+public class DenseEncodedPatternModel implements IModel {
+
+ private static final ResourceLocation BASE_MODEL = FluidCraft.resource("item/dense_encoded_pattern");
+
+ @Override
+ @Nonnull
+ public Collection getDependencies() {
+ return Collections.singletonList(BASE_MODEL);
+ }
+
+ // adapted from ae2's ItemEncodedPatternModel#bake
+ @Override
+ @Nonnull
+ public IBakedModel bake(@Nonnull IModelState state, @Nonnull VertexFormat format, @Nonnull Function bakedTextureGetter) {
+ IBakedModel baseModel;
+ try {
+ baseModel = ModelLoaderRegistry.getModel(BASE_MODEL).bake(state, format, bakedTextureGetter);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return Ae2ReflectClient.bakeEncodedPatternModel(baseModel, PerspectiveMapWrapper.getTransforms(state));
+ }
+
+ public static class Loader implements ICustomModelLoader {
+
+ @Override
+ public void onResourceManagerReload(@Nonnull IResourceManager resourceManager) {
+ // NO-OP
+ }
+
+ @Override
+ public boolean accepts(ResourceLocation modelLocation) {
+ // modelLocation will probably be a ModelResourceLocation, so using compareTo lets us bypass the
+ // ModelResourceLocation equality behaviour and fall back to that of ResourceLocation
+ return modelLocation.compareTo(NameConst.MODEL_DENSE_ENCODED_PATTERN) == 0;
+ }
+
+ @Override
+ @Nonnull
+ public IModel loadModel(@Nonnull ResourceLocation modelLocation) {
+ return new DenseEncodedPatternModel();
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/model/FluidPacketModel.java b/src/main/java/com/glodblock/github/client/model/FluidPacketModel.java
new file mode 100644
index 000000000..5f8ec5dde
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/model/FluidPacketModel.java
@@ -0,0 +1,234 @@
+package com.glodblock.github.client.model;
+
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.util.FluidKey;
+import com.glodblock.github.util.NameConst;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.block.model.*;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.vertex.VertexFormat;
+import net.minecraft.client.resources.IResourceManager;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.world.World;
+import net.minecraftforge.client.model.ICustomModelLoader;
+import net.minecraftforge.client.model.IModel;
+import net.minecraftforge.client.model.ItemLayerModel;
+import net.minecraftforge.common.model.IModelState;
+import net.minecraftforge.common.model.TRSRTransformation;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+import org.apache.commons.lang3.tuple.Pair;
+import org.lwjgl.util.vector.Vector3f;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.vecmath.Matrix4f;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+public class FluidPacketModel implements IModel {
+
+ @SuppressWarnings("deprecation")
+ private static final ItemCameraTransforms CAMERA_TRANSFORMS = new ItemCameraTransforms(
+ new ItemTransformVec3f(new Vector3f(0F, 0F, 0F), new Vector3f(0F, 0.1875F, 0.0625F), new Vector3f(0.55F, 0.55F, 0.55F)),
+ new ItemTransformVec3f(new Vector3f(0F, 0F, 0F), new Vector3f(0F, 0.1875F, 0.0625F), new Vector3f(0.55F, 0.55F, 0.55F)),
+ new ItemTransformVec3f(new Vector3f(0F, -90F, 25F), new Vector3f(0.070625F, 0.2F, 0.070625F), new Vector3f(0.68F, 0.68F, 0.68F)),
+ new ItemTransformVec3f(new Vector3f(0F, -90F, 25F), new Vector3f(0.070625F, 0.2F, 0.070625F), new Vector3f(0.68F, 0.68F, 0.68F)),
+ new ItemTransformVec3f(new Vector3f(0F, 180F, 0F), new Vector3f(0F, 0.8125F, 0.4375F), new Vector3f(1F, 1F, 1F)),
+ ItemTransformVec3f.DEFAULT,
+ new ItemTransformVec3f(new Vector3f(0F, 0F, 0F), new Vector3f(0F, 0.125F, 0F), new Vector3f(0.5F, 0.5F, 0.5F)),
+ new ItemTransformVec3f(new Vector3f(0F, 180F, 0F), new Vector3f(0F, 0F, 0F), new Vector3f(1F, 1F, 1F)));
+
+ @Override
+ @Nonnull
+ public IBakedModel bake(@Nonnull IModelState state, @Nonnull VertexFormat format, @Nonnull Function textureBakery) {
+ return new BakedFluidPacketModel(state, format);
+ }
+
+ public static class Loader implements ICustomModelLoader {
+
+ @Override
+ public void onResourceManagerReload(@Nonnull IResourceManager resourceManager) {
+ // NO-OP
+ }
+
+ @Override
+ public boolean accepts(ResourceLocation modelLocation) {
+ // modelLocation will probably be a ModelResourceLocation, so using compareTo lets us bypass the
+ // ModelResourceLocation equality behaviour and fall back to that of ResourceLocation
+ return modelLocation.compareTo(NameConst.MODEL_FLUID_PACKET) == 0;
+ }
+
+ @Override
+ @Nonnull
+ public IModel loadModel(@Nonnull ResourceLocation modelLocation) {
+ return new FluidPacketModel();
+ }
+
+ }
+
+ private static class BakedFluidPacketModel implements IBakedModel {
+
+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+ private final Optional modelTransform;
+ private final VertexFormat vertexFormat;
+ private final OverrideCache overrides;
+ private final OverrideCache.OverrideModel defaultOverride;
+
+ public BakedFluidPacketModel(IModelState modelState, VertexFormat vertexFormat) {
+ this.modelTransform = modelState.apply(Optional.empty());
+ this.vertexFormat = vertexFormat;
+ this.overrides = new OverrideCache();
+ this.defaultOverride = overrides.resolve(new FluidStack(FluidRegistry.WATER, Fluid.BUCKET_VOLUME));
+ }
+
+ @Override
+ @Nonnull
+ public List getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand) {
+ return defaultOverride.getQuads(state, side, rand);
+ }
+
+ @Override
+ public boolean isAmbientOcclusion() {
+ return defaultOverride.isAmbientOcclusion();
+ }
+
+ @Override
+ public boolean isGui3d() {
+ return defaultOverride.isGui3d();
+ }
+
+ @Override
+ public boolean isBuiltInRenderer() {
+ return defaultOverride.isBuiltInRenderer();
+ }
+
+ @Override
+ @Nonnull
+ public TextureAtlasSprite getParticleTexture() {
+ return defaultOverride.getParticleTexture();
+ }
+
+ @Override
+ public boolean isAmbientOcclusion(@Nonnull IBlockState state) {
+ return defaultOverride.isAmbientOcclusion(state);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ @Nonnull
+ public ItemCameraTransforms getItemCameraTransforms() {
+ return defaultOverride.getItemCameraTransforms();
+ }
+
+ @Override
+ @Nonnull
+ public Pair extends IBakedModel, Matrix4f> handlePerspective(@Nonnull ItemCameraTransforms.TransformType cameraTransformType) {
+ return defaultOverride.handlePerspective(cameraTransformType);
+ }
+
+ @Override
+ @Nonnull
+ public ItemOverrideList getOverrides() {
+ return overrides;
+ }
+
+ private class OverrideCache extends ItemOverrideList {
+
+ private final Cache cache = CacheBuilder.newBuilder()
+ .maximumSize(1000) // cache params borrowed from Tinkers' Construct model system, which is under MIT
+ .expireAfterWrite(5, TimeUnit.MINUTES)
+ .build();
+
+ OverrideCache() {
+ super(Collections.emptyList());
+ }
+
+ @Override
+ @Nonnull
+ public IBakedModel handleItemState(@Nonnull IBakedModel originalModel, ItemStack stack,
+ @Nullable World world, @Nullable EntityLivingBase entity) {
+ if (!(stack.getItem() instanceof ItemFluidPacket)) {
+ return originalModel;
+ }
+ FluidStack fluid = ItemFluidPacket.getFluidStack(stack);
+ return fluid != null ? resolve(fluid) : originalModel;
+ }
+
+ OverrideModel resolve(FluidStack fluid) {
+ try {
+ return cache.get(new FluidKey(fluid), () -> new OverrideModel(fluid));
+ } catch (ExecutionException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ class OverrideModel implements IBakedModel {
+
+ private final TextureAtlasSprite texture;
+ private final List quads;
+
+ OverrideModel(FluidStack fluidStack) {
+ this.texture = Minecraft.getMinecraft().getTextureMapBlocks()
+ .getAtlasSprite(fluidStack.getFluid().getStill(fluidStack).toString());
+ this.quads = ItemLayerModel.getQuadsForSprite(1, texture, vertexFormat, modelTransform);
+ }
+
+ @Override
+ @Nonnull
+ public List getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand) {
+ return quads;
+ }
+
+ @Override
+ public boolean isAmbientOcclusion() {
+ return false;
+ }
+
+ @Override
+ public boolean isGui3d() {
+ return false;
+ }
+
+ @Override
+ public boolean isBuiltInRenderer() {
+ return false;
+ }
+
+ @Override
+ @Nonnull
+ public TextureAtlasSprite getParticleTexture() {
+ return texture;
+ }
+
+ @Override
+ @Nonnull
+ @SuppressWarnings("deprecation")
+ public ItemCameraTransforms getItemCameraTransforms() {
+ return CAMERA_TRANSFORMS;
+ }
+
+ @Override
+ @Nonnull
+ public ItemOverrideList getOverrides() {
+ return OverrideCache.this;
+ }
+
+ }
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/render/DropColourHandler.java b/src/main/java/com/glodblock/github/client/render/DropColourHandler.java
new file mode 100644
index 000000000..bc8bd8f5b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/render/DropColourHandler.java
@@ -0,0 +1,62 @@
+package com.glodblock.github.client.render;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraftforge.client.event.TextureStitchEvent;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DropColourHandler {
+
+ private final Map colourCache = new HashMap<>();
+
+ @SubscribeEvent
+ public void onTextureMapStitch(TextureStitchEvent event) {
+ if (event.getMap() == Minecraft.getMinecraft().getTextureMapBlocks()) {
+ colourCache.clear();
+ }
+ }
+
+ public int getColour(FluidStack fluidStack) {
+ Fluid fluid = fluidStack.getFluid();
+ int colour = fluid.getColor(fluidStack);
+ return colour != -1 ? colour : getColour(fluid);
+ }
+
+ public int getColour(Fluid fluid) {
+ Integer cached = colourCache.get(fluid.getName());
+ if (cached != null) {
+ return cached;
+ }
+ int colour = fluid.getColor();
+ if (colour == -1) {
+ TextureAtlasSprite sprite = Minecraft.getMinecraft().getTextureMapBlocks()
+ .getTextureExtry(fluid.getStill().toString());
+ if (sprite != null && sprite.getFrameCount() > 0) {
+ int[][] image = sprite.getFrameTextureData(0);
+ int r = 0, g = 0, b = 0, count = 0;
+ for (int[] row : image) {
+ for (int pixel : row) {
+ if (((pixel >> 24) & 0xFF) > 127) { // is alpha above 50%?
+ r += (pixel >> 16) & 0xFF;
+ g += (pixel >> 8) & 0xFF;
+ b += pixel & 0xFF;
+ ++count;
+ }
+ }
+ }
+ if (count > 0) {
+ // probably shouldn't need to mask each component by 0xFF
+ colour = ((r / count) << 16) | ((g / count) << 8) | (b / count);
+ }
+ }
+ }
+ colourCache.put(fluid.getName(), colour);
+ return colour;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/render/FluidRenderUtils.java b/src/main/java/com/glodblock/github/client/render/FluidRenderUtils.java
new file mode 100644
index 000000000..add1ac295
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/render/FluidRenderUtils.java
@@ -0,0 +1,127 @@
+package com.glodblock.github.client.render;
+
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.client.render.StackSizeRenderer;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+import org.lwjgl.opengl.GL11;
+
+import javax.annotation.Nullable;
+
+public class FluidRenderUtils {
+
+ @Nullable
+ public static TextureAtlasSprite prepareRender(@Nullable FluidStack fluidStack) {
+ if (fluidStack == null) {
+ return null;
+ }
+ Fluid fluid = fluidStack.getFluid();
+ TextureAtlasSprite sprite = Minecraft.getMinecraft().getTextureMapBlocks()
+ .getAtlasSprite(fluid.getStill(fluidStack).toString());
+ int colour = fluid.getColor(fluidStack);
+ GlStateManager.color(
+ ((colour >> 16) & 0xFF) / 255F,
+ ((colour >> 8) & 0xFF) / 255F,
+ (colour & 0xFF) / 255F,
+ ((colour >> 24) & 0xFF) / 255F);
+ return sprite;
+ }
+
+ private static void doRenderFluid(Tessellator tess, BufferBuilder buf, int x, int y, int width, int height,
+ TextureAtlasSprite sprite, double fraction) {
+ GlStateManager.enableBlend();
+ GlStateManager.blendFunc(
+ GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
+ int fluidHeight = Math.round(height * (float)Math.min(1D, Math.max(0D, fraction)));
+ double x2 = x + width;
+ while (fluidHeight > 0) {
+ buf.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
+ double y1 = y + height - fluidHeight, y2 = y1 + Math.min(fluidHeight, width);
+ double u1 = sprite.getMinU(), v1 = sprite.getMinV(), u2 = sprite.getMaxU(), v2 = sprite.getMaxV();
+ if (fluidHeight < width) {
+ v2 = v1 + (v2 - v1) * (fluidHeight / (double)width);
+ fluidHeight = 0;
+ } else {
+ //noinspection SuspiciousNameCombination
+ fluidHeight -= width;
+ }
+ buf.pos(x, y1, 0D).tex(u1, v1).endVertex();
+ buf.pos(x, y2, 0D).tex(u1, v2).endVertex();
+ buf.pos(x2, y2, 0D).tex(u2, v2).endVertex();
+ buf.pos(x2, y1, 0D).tex(u2, v1).endVertex();
+ tess.draw();
+ }
+ }
+
+ public static void renderFluidIntoGui(Tessellator tess, BufferBuilder buf, int x, int y, int width, int height,
+ @Nullable IAEFluidStack aeFluidStack, int capacity) {
+ if (aeFluidStack != null) {
+ TextureAtlasSprite sprite = FluidRenderUtils.prepareRender(aeFluidStack.getFluidStack());
+ if (sprite != null) {
+ doRenderFluid(tess, buf, x, y, width, height, sprite, aeFluidStack.getStackSize() / (double)capacity);
+ }
+ }
+ }
+
+ public static void renderFluidIntoGui(Tessellator tess, BufferBuilder buf, int x, int y, int width, int height,
+ @Nullable FluidStack fluidStack, int capacity) {
+ if (fluidStack != null) {
+ TextureAtlasSprite sprite = FluidRenderUtils.prepareRender(fluidStack);
+ if (sprite != null) {
+ doRenderFluid(tess, buf, x, y, width, height, sprite, fluidStack.amount / (double)capacity);
+ }
+ }
+ }
+
+ public static void renderFluidIntoGuiCleanly(int x, int y, int width, int height,
+ @Nullable IAEFluidStack aeFluidStack, int capacity) {
+ Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
+ Tessellator tess = Tessellator.getInstance();
+ renderFluidIntoGui(tess, tess.getBuffer(), x, y, width, height, aeFluidStack, capacity);
+ GlStateManager.color(1F, 1F, 1F, 1F);
+ }
+
+ public static void renderFluidIntoGuiCleanly(int x, int y, int width, int height,
+ @Nullable FluidStack fluidStack, int capacity) {
+ Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
+ Tessellator tess = Tessellator.getInstance();
+ renderFluidIntoGui(tess, tess.getBuffer(), x, y, width, height, fluidStack, capacity);
+ GlStateManager.color(1F, 1F, 1F, 1F);
+ }
+
+ public static boolean renderFluidIntoGuiSlot(Slot slot, @Nullable FluidStack fluid,
+ StackSizeRenderer stackSizeRenderer, FontRenderer fontRenderer) {
+ if (fluid == null || fluid.amount <= 0) {
+ return false;
+ }
+ renderFluidIntoGuiCleanly(slot.xPos, slot.yPos, 16, 16, fluid, fluid.amount);
+ stackSizeRenderer.renderStackSize(fontRenderer, ItemFluidDrop.newAeStack(fluid), slot.xPos, slot.yPos);
+ return true;
+ }
+
+ public static boolean renderFluidPacketIntoGuiSlot(Slot slot, @Nullable IAEItemStack stack,
+ StackSizeRenderer stackSizeRenderer, FontRenderer fontRenderer) {
+ return stack != null && stack.getItem() instanceof ItemFluidPacket
+ && renderFluidIntoGuiSlot(slot, ItemFluidPacket.getFluidStack(stack), stackSizeRenderer, fontRenderer);
+ }
+
+ public static boolean renderFluidPacketIntoGuiSlot(Slot slot, ItemStack stack,
+ StackSizeRenderer stackSizeRenderer, FontRenderer fontRenderer) {
+ return !stack.isEmpty() && stack.getItem() instanceof ItemFluidPacket
+ && renderFluidIntoGuiSlot(slot, ItemFluidPacket.getFluidStack(stack), stackSizeRenderer, fontRenderer);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java b/src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java
new file mode 100644
index 000000000..656b89ac7
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java
@@ -0,0 +1,81 @@
+package com.glodblock.github.client.render;
+
+import com.glodblock.github.common.tile.TileIngredientBuffer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.RenderItem;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.capability.IFluidTankProperties;
+import net.minecraftforge.items.IItemHandler;
+import org.lwjgl.opengl.GL11;
+
+public class RenderIngredientBuffer extends TileEntitySpecialRenderer {
+
+ private static final double d = 0.45D;
+
+ @Override
+ public void render(TileIngredientBuffer tile, double x, double y, double z, float partialTicks, int destroyStage, float alpha) {
+ GlStateManager.enableBlend();
+ GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
+ GlStateManager.enableLighting();
+ GlStateManager.pushMatrix();
+ GlStateManager.translate(x + 0.5D, y + 0.5D, z + 0.5D);
+ bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
+
+ IItemHandler inv = tile.getInternalInventory();
+ for (int i = 0; i < inv.getSlots(); i++) {
+ ItemStack stack = inv.getStackInSlot(i);
+ if (!stack.isEmpty()) {
+ GlStateManager.pushMatrix();
+ float scale = stack.getItem() instanceof ItemBlock ? 0.36F : 0.64F;
+ GlStateManager.scale(scale, scale, scale);
+ GlStateManager.rotate((getWorld().getTotalWorldTime() + partialTicks) * 6F, 0F, 1F, 0F);
+ RenderItem renderer = Minecraft.getMinecraft().getRenderItem();
+ renderer.renderItem(stack, renderer.getItemModelWithOverrides(stack, getWorld(), null));
+ GlStateManager.popMatrix();
+ break;
+ }
+ }
+
+ for (IFluidTankProperties tank : tile.getFluidInventory().getTankProperties()) {
+ TextureAtlasSprite fluidSprite = FluidRenderUtils.prepareRender(tank.getContents());
+ if (fluidSprite != null) {
+ Tessellator tess = Tessellator.getInstance();
+ BufferBuilder buf = tess.getBuffer();
+ // not necessarily the most efficient way to draw a cube, but probably the least tedious
+ GlStateManager.pushMatrix();
+ drawFace(tess, buf, fluidSprite);
+ for (int i = 0; i < 3; i++) {
+ GlStateManager.rotate(90F, 1F, 0F, 0F);
+ drawFace(tess, buf, fluidSprite);
+ }
+ GlStateManager.rotate(90F, 0F, 0F, 1F);
+ drawFace(tess, buf, fluidSprite);
+ GlStateManager.rotate(180F, 0F, 0F, 1F);
+ drawFace(tess, buf, fluidSprite);
+ GlStateManager.popMatrix();
+ break;
+ }
+ }
+
+ GlStateManager.color(1F, 1F, 1F, 1F);
+ GlStateManager.popMatrix();
+ }
+
+ private static void drawFace(Tessellator tess, BufferBuilder buf, TextureAtlasSprite sprite) {
+ buf.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
+ buf.pos(-d, d, -d).tex(sprite.getMinU(), sprite.getMinV()).endVertex();
+ buf.pos(-d, d, d).tex(sprite.getMinU(), sprite.getMaxV()).endVertex();
+ buf.pos(d, d, d).tex(sprite.getMaxU(), sprite.getMaxV()).endVertex();
+ buf.pos(d, d, -d).tex(sprite.getMaxU(), sprite.getMinV()).endVertex();
+ tess.draw();
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/block/BlockBurette.java b/src/main/java/com/glodblock/github/common/block/BlockBurette.java
new file mode 100644
index 000000000..583a5e81c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockBurette.java
@@ -0,0 +1,38 @@
+package com.glodblock.github.common.block;
+
+import appeng.block.AEBaseTileBlock;
+import com.glodblock.github.common.tile.TileBurette;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import net.minecraft.block.material.Material;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+public class BlockBurette extends AEBaseTileBlock {
+
+ public BlockBurette() {
+ super(Material.IRON);
+ setTileEntity(TileBurette.class);
+ }
+
+ @Override
+ public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand,
+ EnumFacing facing, float hitX, float hitY, float hitZ) {
+ if (player.isSneaking()) {
+ return false;
+ }
+ TileBurette tile = getTileEntity(world, pos);
+ if (tile != null) {
+ if (!world.isRemote) {
+ InventoryHandler.openGui(player, world, pos, facing, GuiType.PRECISION_BURETTE);
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/block/BlockDualInterface.java b/src/main/java/com/glodblock/github/common/block/BlockDualInterface.java
new file mode 100644
index 000000000..6dee9140c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockDualInterface.java
@@ -0,0 +1,85 @@
+package com.glodblock.github.common.block;
+
+import appeng.api.util.IOrientable;
+import appeng.block.AEBaseTileBlock;
+import appeng.util.Platform;
+import com.glodblock.github.common.tile.TileDualInterface;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import net.minecraft.block.material.Material;
+import net.minecraft.block.properties.IProperty;
+import net.minecraft.block.properties.PropertyBool;
+import net.minecraft.block.properties.PropertyDirection;
+import net.minecraft.block.state.BlockStateContainer;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.IBlockAccess;
+import net.minecraft.world.World;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class BlockDualInterface extends AEBaseTileBlock {
+
+ private static final PropertyBool OMNIDIRECTIONAL = PropertyBool.create("omnidirectional");
+ private static final PropertyDirection FACING = PropertyDirection.create("facing");
+
+ public BlockDualInterface() {
+ super(Material.IRON);
+ this.setTileEntity(TileDualInterface.class);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected IProperty[] getAEStates() {
+ return new IProperty[] { OMNIDIRECTIONAL };
+ }
+
+ @Override
+ protected BlockStateContainer createBlockState() {
+ return new BlockStateContainer(this, OMNIDIRECTIONAL, FACING);
+ }
+
+ @Override
+ public boolean onActivated(final World w, final BlockPos pos, final EntityPlayer p,
+ final EnumHand hand, final @Nullable ItemStack heldItem, final EnumFacing side,
+ final float hitX, final float hitY, final float hitZ) {
+ if (p.isSneaking()) {
+ return false;
+ }
+ final TileDualInterface tg = this.getTileEntity(w, pos);
+ if (tg != null) {
+ if (Platform.isServer()) {
+ InventoryHandler.openGui(p, w, pos, side, GuiType.DUAL_ITEM_INTERFACE);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ @Nonnull
+ public IBlockState getActualState(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos) {
+ TileDualInterface te = this.getTileEntity(world, pos);
+ return te == null ? state
+ : state.withProperty(OMNIDIRECTIONAL, te.isOmniDirectional()).withProperty(FACING, te.getForward());
+ }
+
+ @Override
+ protected boolean hasCustomRotation() {
+ return true;
+ }
+
+ @Override
+ protected void customRotateBlock(final IOrientable rotatable, final EnumFacing axis) {
+ if (rotatable instanceof TileDualInterface) {
+ ((TileDualInterface)rotatable).setSide(axis);
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/block/BlockFluidDiscretizer.java b/src/main/java/com/glodblock/github/common/block/BlockFluidDiscretizer.java
new file mode 100644
index 000000000..16fbbbadd
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockFluidDiscretizer.java
@@ -0,0 +1,14 @@
+package com.glodblock.github.common.block;
+
+import appeng.block.AEBaseTileBlock;
+import com.glodblock.github.common.tile.TileFluidDiscretizer;
+import net.minecraft.block.material.Material;
+
+public class BlockFluidDiscretizer extends AEBaseTileBlock {
+
+ public BlockFluidDiscretizer() {
+ super(Material.IRON);
+ setTileEntity(TileFluidDiscretizer.class);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/block/BlockFluidPacketDecoder.java b/src/main/java/com/glodblock/github/common/block/BlockFluidPacketDecoder.java
new file mode 100644
index 000000000..243630141
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockFluidPacketDecoder.java
@@ -0,0 +1,38 @@
+package com.glodblock.github.common.block;
+
+import appeng.block.AEBaseTileBlock;
+import com.glodblock.github.common.tile.TileFluidPacketDecoder;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import net.minecraft.block.material.Material;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+public class BlockFluidPacketDecoder extends AEBaseTileBlock {
+
+ public BlockFluidPacketDecoder() {
+ super(Material.IRON);
+ setTileEntity(TileFluidPacketDecoder.class);
+ }
+
+ @Override
+ public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand,
+ EnumFacing facing, float hitX, float hitY, float hitZ) {
+ if (player.isSneaking()) {
+ return false;
+ }
+ TileFluidPacketDecoder tile = getTileEntity(world, pos);
+ if (tile != null) {
+ if (!world.isRemote) {
+ InventoryHandler.openGui(player, world, pos, facing, GuiType.FLUID_PACKET_DECODER);
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/common/block/BlockFluidPatternEncoder.java b/src/main/java/com/glodblock/github/common/block/BlockFluidPatternEncoder.java
new file mode 100644
index 000000000..4b84a3bf3
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockFluidPatternEncoder.java
@@ -0,0 +1,38 @@
+package com.glodblock.github.common.block;
+
+import appeng.block.AEBaseTileBlock;
+import com.glodblock.github.common.tile.TileFluidPatternEncoder;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import net.minecraft.block.material.Material;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+public class BlockFluidPatternEncoder extends AEBaseTileBlock {
+
+ public BlockFluidPatternEncoder() {
+ super(Material.IRON);
+ setTileEntity(TileFluidPatternEncoder.class);
+ }
+
+ @Override
+ public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand,
+ EnumFacing facing, float hitX, float hitY, float hitZ) {
+ if (player.isSneaking()) {
+ return false;
+ }
+ TileFluidPatternEncoder tile = getTileEntity(world, pos);
+ if (tile != null) {
+ if (!world.isRemote) {
+ InventoryHandler.openGui(player, world, pos, facing, GuiType.FLUID_PATTERN_ENCODER);
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java b/src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java
new file mode 100644
index 000000000..f01dd2f7e
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java
@@ -0,0 +1,58 @@
+package com.glodblock.github.common.block;
+
+import appeng.block.AEBaseTileBlock;
+import com.glodblock.github.common.tile.TileIngredientBuffer;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import net.minecraft.block.material.Material;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.BlockRenderLayer;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
+
+import javax.annotation.Nonnull;
+
+public class BlockIngredientBuffer extends AEBaseTileBlock {
+
+ public BlockIngredientBuffer() {
+ super(Material.IRON);
+ setTileEntity(TileIngredientBuffer.class);
+ setOpaque(false);
+ this.lightOpacity = 4;
+ }
+
+ @Override
+ public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand,
+ EnumFacing facing, float hitX, float hitY, float hitZ) {
+ if (player.isSneaking()) {
+ return false;
+ }
+ TileIngredientBuffer tile = getTileEntity(world, pos);
+ if (tile != null) {
+ if (!world.isRemote) {
+ InventoryHandler.openGui(player, world, pos, facing, GuiType.INGREDIENT_BUFFER);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ @Nonnull
+ public BlockRenderLayer getRenderLayer() {
+ return BlockRenderLayer.CUTOUT;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean isFullCube(@Nonnull IBlockState state) {
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/component/DualityDualInterface.java b/src/main/java/com/glodblock/github/common/component/DualityDualInterface.java
new file mode 100644
index 000000000..b9040d06a
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/component/DualityDualInterface.java
@@ -0,0 +1,184 @@
+package com.glodblock.github.common.component;
+
+import appeng.api.config.Actionable;
+import appeng.api.config.Upgrades;
+import appeng.api.networking.IGridNode;
+import appeng.api.networking.crafting.ICraftingLink;
+import appeng.api.networking.crafting.ICraftingPatternDetails;
+import appeng.api.networking.crafting.ICraftingProviderHelper;
+import appeng.api.networking.events.MENetworkChannelsChanged;
+import appeng.api.networking.events.MENetworkPowerStatusChange;
+import appeng.api.networking.ticking.TickRateModulation;
+import appeng.api.networking.ticking.TickingRequest;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.api.util.IConfigManager;
+import appeng.fluids.helper.DualityFluidInterface;
+import appeng.fluids.helper.IFluidInterfaceHost;
+import appeng.helpers.DualityInterface;
+import appeng.helpers.IInterfaceHost;
+import appeng.me.helpers.AENetworkProxy;
+import appeng.util.inv.InvOperation;
+import com.google.common.collect.ImmutableSet;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.EnumFacing;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.common.capabilities.ICapabilityProvider;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+
+public class DualityDualInterface implements ICapabilityProvider {
+
+ private final DualityInterface itemDuality;
+ private final DualityFluidInterface fluidDuality;
+
+ public DualityDualInterface(AENetworkProxy networkProxy, H host) {
+ this.itemDuality = new DualityInterface(networkProxy, host);
+ this.fluidDuality = new DualityFluidInterface(networkProxy, host);
+ }
+
+ public DualityInterface getItemInterface() {
+ return itemDuality;
+ }
+
+ public DualityFluidInterface getFluidInterface() {
+ return fluidDuality;
+ }
+
+ public IConfigManager getConfigManager() {
+ return itemDuality.getConfigManager(); // fluid interface has no meaningful config, so this is fine
+ }
+
+ public int getInstalledUpgrades(final Upgrades u) {
+ return itemDuality.getInstalledUpgrades(u); // fluid interface supports no upgrades, so this is fine
+ }
+
+ public int getPriority() {
+ return itemDuality.getPriority(); // both ifaces should always have the same prio
+ }
+
+ public void setPriority(final int newValue) {
+ itemDuality.setPriority(newValue);
+ fluidDuality.setPriority(newValue);
+ }
+
+ @Override
+ public boolean hasCapability(@Nonnull Capability> capability, @Nullable EnumFacing facing) {
+ return itemDuality.hasCapability(capability, facing) || fluidDuality.hasCapability(capability, facing);
+ }
+
+ @Nullable
+ @Override
+ public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) {
+ T capInst = itemDuality.getCapability(capability, facing);
+ return capInst != null ? capInst : fluidDuality.getCapability(capability, facing);
+ }
+
+ // dual behaviour
+
+ public void initialize() {
+ itemDuality.initialize();
+ }
+
+ public TickingRequest getTickingRequest(IGridNode node) {
+ TickingRequest item = itemDuality.getTickingRequest(node), fluid = fluidDuality.getTickingRequest(node);
+ return new TickingRequest(
+ Math.min(item.minTickRate, fluid.minTickRate),
+ Math.max(item.maxTickRate, fluid.maxTickRate),
+ item.isSleeping && fluid.isSleeping, // might cause some unnecessary ticking, but oh well
+ true);
+ }
+
+ public TickRateModulation onTick(IGridNode node, int ticksSinceLastCall) {
+ TickRateModulation item = itemDuality.tickingRequest(node, ticksSinceLastCall);
+ TickRateModulation fluid = fluidDuality.tickingRequest(node, ticksSinceLastCall);
+ if (item.ordinal() >= fluid.ordinal()) { // return whichever is most urgent
+ return item;
+ } else {
+ return fluid;
+ }
+ }
+
+ public void onChannelStateChange(final MENetworkChannelsChanged c) {
+ itemDuality.notifyNeighbors();
+ fluidDuality.notifyNeighbors();
+ }
+
+ public void onPowerStateChange(final MENetworkPowerStatusChange c) {
+ itemDuality.notifyNeighbors();
+ fluidDuality.notifyNeighbors();
+ }
+
+ public void onGridChanged() {
+ itemDuality.gridChanged();
+ fluidDuality.gridChanged();
+ }
+
+ // item interface behaviour
+
+ public void addDrops(List drops) {
+ itemDuality.addDrops(drops);
+ }
+
+ public boolean canInsertItem(ItemStack stack) {
+ return itemDuality.canInsert(stack);
+ }
+
+ public IItemHandler getItemInventoryByName(String name) {
+ return itemDuality.getInventoryByName(name);
+ }
+
+ public IItemHandler getInternalItemInventory() {
+ return itemDuality.getInternalInventory();
+ }
+
+ public void onItemInventoryChange(IItemHandler inv, int slot, InvOperation op, ItemStack removed, ItemStack added) {
+ itemDuality.onChangeInventory(inv, slot, op, removed, added);
+ }
+
+ // autocrafting
+
+ public boolean pushPattern(ICraftingPatternDetails patternDetails, InventoryCrafting table) {
+ return itemDuality.pushPattern(patternDetails, table);
+ }
+
+ public boolean isCraftingBusy() {
+ return itemDuality.isBusy();
+ }
+
+ public void provideCrafting(ICraftingProviderHelper craftingTracker) {
+ itemDuality.provideCrafting(craftingTracker);
+ }
+
+ public ImmutableSet getRequestCraftingJobs() {
+ return itemDuality.getRequestedJobs();
+ }
+
+ public IAEItemStack injectCraftedItems(ICraftingLink link, IAEItemStack items, Actionable mode) {
+ return itemDuality.injectCraftedItems(link, items, mode);
+ }
+
+ public void onCraftingJobStateChange(ICraftingLink link) {
+ itemDuality.jobStateChange(link);
+ }
+
+ // serialization
+
+ public void writeToNBT(final NBTTagCompound data) {
+ NBTTagCompound itemIfaceTag = new NBTTagCompound(), fluidIfaceTag = new NBTTagCompound();
+ itemDuality.writeToNBT(itemIfaceTag);
+ fluidDuality.writeToNBT(fluidIfaceTag);
+ data.setTag("itemDuality", itemIfaceTag);
+ data.setTag("fluidDuality", fluidIfaceTag);
+ }
+
+ public void readFromNBT(final NBTTagCompound data) {
+ itemDuality.readFromNBT(data.getCompoundTag("itemDuality"));
+ fluidDuality.readFromNBT(data.getCompoundTag("fluidDuality"));
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/item/ItemFluidDrop.java b/src/main/java/com/glodblock/github/common/item/ItemFluidDrop.java
new file mode 100644
index 000000000..c290ef1c5
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemFluidDrop.java
@@ -0,0 +1,131 @@
+package com.glodblock.github.common.item;
+
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.fluids.util.AEFluidStack;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.client.util.ITooltipFlag;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.NonNullList;
+import net.minecraft.util.text.TextFormatting;
+import net.minecraft.util.text.translation.I18n;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.Constants;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Objects;
+
+public class ItemFluidDrop extends Item {
+
+ @Override
+ public void getSubItems(@Nonnull CreativeTabs tab,@Nonnull NonNullList items) {
+ if (isInCreativeTab(tab)) {
+ items.add(newStack(new FluidStack(FluidRegistry.WATER, 1)));
+ items.add(newStack(new FluidStack(FluidRegistry.LAVA, 1)));
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ @Nonnull
+ public String getItemStackDisplayName(@Nonnull ItemStack stack) {
+ FluidStack fluid = getFluidStack(stack);
+ // would like to use I18n::format instead of this deprecated function, but that only exists on the client :/
+ return I18n.translateToLocalFormatted(getTranslationKey(stack) + ".name", fluid != null ? fluid.getLocalizedName() : "???");
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void addInformation(@Nonnull ItemStack stack, @Nullable World world, @Nonnull List tooltip, @Nonnull ITooltipFlag flags) {
+ FluidStack fluid = getFluidStack(stack);
+ if (fluid != null) {
+ tooltip.add(String.format(TextFormatting.GRAY + "%s, 1 mB", fluid.getLocalizedName()));
+ } else {
+ tooltip.add(TextFormatting.RED + I18n.translateToLocal(NameConst.TT_INVALID_FLUID));
+ }
+ }
+
+ @Nullable
+ public static FluidStack getFluidStack(ItemStack stack) {
+ if (stack.isEmpty() || stack.getItem() != FCItems.FLUID_DROP || !stack.hasTagCompound()) {
+ return null;
+ }
+ NBTTagCompound tag = Objects.requireNonNull(stack.getTagCompound());
+ if (!tag.hasKey("Fluid", Constants.NBT.TAG_STRING)) {
+ return null;
+ }
+ Fluid fluid = FluidRegistry.getFluid(tag.getString("Fluid"));
+ if (fluid == null) {
+ return null;
+ }
+ FluidStack fluidStack = new FluidStack(fluid, stack.getCount());
+ if (tag.hasKey("FluidTag", Constants.NBT.TAG_COMPOUND)) {
+ fluidStack.tag = tag.getCompoundTag("FluidTag");
+ }
+ return fluidStack;
+ }
+
+ @Nullable
+ public static IAEFluidStack getAeFluidStack(@Nullable IAEItemStack stack) {
+ if (stack == null) {
+ return null;
+ }
+ IAEFluidStack fluidStack = AEFluidStack.fromFluidStack(getFluidStack(stack.getDefinition()));
+ if (fluidStack == null) {
+ return null;
+ }
+ fluidStack.setStackSize(stack.getStackSize());
+ return fluidStack;
+ }
+
+ public static ItemStack newStack(@Nullable FluidStack fluid) {
+ if (fluid == null || fluid.amount <= 0) {
+ return ItemStack.EMPTY;
+ }
+ ItemStack stack = new ItemStack(FCItems.FLUID_DROP, fluid.amount);
+ NBTTagCompound tag = new NBTTagCompound();
+ tag.setString("Fluid", fluid.getFluid().getName());
+ if (fluid.tag != null) {
+ tag.setTag("FluidTag", fluid.tag);
+ }
+ stack.setTagCompound(tag);
+ return stack;
+ }
+
+ @Nullable
+ public static IAEItemStack newAeStack(@Nullable FluidStack fluid) {
+ if (fluid == null || fluid.amount <= 0) {
+ return null;
+ }
+ IAEItemStack stack = AEItemStack.fromItemStack(newStack(fluid));
+ if (stack == null) {
+ return null;
+ }
+ stack.setStackSize(fluid.amount);
+ return stack;
+ }
+
+ @Nullable
+ public static IAEItemStack newAeStack(@Nullable IAEFluidStack fluid) {
+ if (fluid == null || fluid.getStackSize() <= 0) {
+ return null;
+ }
+ IAEItemStack stack = AEItemStack.fromItemStack(newStack(fluid.getFluidStack()));
+ if (stack == null) {
+ return null;
+ }
+ stack.setStackSize(fluid.getStackSize());
+ return stack;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/common/item/ItemFluidEncodedPattern.java b/src/main/java/com/glodblock/github/common/item/ItemFluidEncodedPattern.java
new file mode 100644
index 000000000..63c00dd36
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemFluidEncodedPattern.java
@@ -0,0 +1,35 @@
+package com.glodblock.github.common.item;
+
+import appeng.api.networking.crafting.ICraftingPatternDetails;
+import appeng.items.misc.ItemEncodedPattern;
+import com.glodblock.github.interfaces.HasCustomModel;
+import com.glodblock.github.util.FluidPatternDetails;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.NonNullList;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.world.World;
+
+import javax.annotation.Nullable;
+
+public class ItemFluidEncodedPattern extends ItemEncodedPattern implements HasCustomModel {
+
+ @Override
+ protected void getCheckedSubItems(CreativeTabs creativeTab, NonNullList itemStacks) {
+ // NO-OP
+ }
+
+ @Nullable
+ @Override
+ public ICraftingPatternDetails getPatternForItem(ItemStack is, World w) {
+ FluidPatternDetails pattern = new FluidPatternDetails(is);
+ return pattern.readFromStack() ? pattern : null;
+ }
+
+ @Override
+ public ResourceLocation getCustomModelPath() {
+ return NameConst.MODEL_DENSE_ENCODED_PATTERN;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/common/item/ItemFluidPacket.java b/src/main/java/com/glodblock/github/common/item/ItemFluidPacket.java
new file mode 100644
index 000000000..ad27784a6
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemFluidPacket.java
@@ -0,0 +1,94 @@
+package com.glodblock.github.common.item;
+
+import appeng.api.storage.data.IAEItemStack;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.interfaces.HasCustomModel;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.client.util.ITooltipFlag;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.NonNullList;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.TextFormatting;
+import net.minecraft.util.text.translation.I18n;
+import net.minecraft.world.World;
+import net.minecraftforge.fluids.FluidStack;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Objects;
+
+public class ItemFluidPacket extends Item implements HasCustomModel {
+
+ public ItemFluidPacket() {
+ setMaxStackSize(1);
+ }
+
+ @Override
+ public void getSubItems(@Nonnull CreativeTabs tab,@Nonnull NonNullList items) {
+ // NO-OP
+ }
+
+ @Override
+ @Nonnull
+ public String getItemStackDisplayName(@Nonnull ItemStack stack) {
+ FluidStack fluid = getFluidStack(stack);
+ return fluid != null ? String.format("%s, %,d mB", fluid.getLocalizedName(), fluid.amount)
+ : super.getItemStackDisplayName(stack);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void addInformation(@Nonnull ItemStack stack, @Nullable World world, @Nonnull List tooltip, @Nonnull ITooltipFlag flags) {
+ FluidStack fluid = getFluidStack(stack);
+ if (fluid != null) {
+ for (String line : I18n.translateToLocal(NameConst.TT_FLUID_PACKET).split("\\\\n")) {
+ tooltip.add(TextFormatting.GRAY + line);
+ }
+ } else {
+ tooltip.add(TextFormatting.RED + I18n.translateToLocal(NameConst.TT_INVALID_FLUID));
+ }
+ }
+
+ @Nullable
+ public static FluidStack getFluidStack(ItemStack stack) {
+ if (stack.isEmpty() || !stack.hasTagCompound()) {
+ return null;
+ }
+ FluidStack fluid = FluidStack.loadFluidStackFromNBT(Objects.requireNonNull(stack.getTagCompound()).getCompoundTag("FluidStack"));
+ return (fluid != null && fluid.amount > 0) ? fluid : null;
+ }
+
+ @Nullable
+ public static FluidStack getFluidStack(@Nullable IAEItemStack stack) {
+ return stack != null ? getFluidStack(stack.getDefinition()) : null;
+ }
+
+ public static ItemStack newStack(@Nullable FluidStack fluid) {
+ if (fluid == null || fluid.amount == 0) {
+ return ItemStack.EMPTY;
+ }
+ ItemStack stack = new ItemStack(FCItems.FLUID_PACKET);
+ NBTTagCompound tag = new NBTTagCompound();
+ NBTTagCompound fluidTag = new NBTTagCompound();
+ fluid.writeToNBT(fluidTag);
+ tag.setTag("FluidStack", fluidTag);
+ stack.setTagCompound(tag);
+ return stack;
+ }
+
+ @Nullable
+ public static IAEItemStack newAeStack(@Nullable FluidStack fluid) {
+ return AEItemStack.fromItemStack(newStack(fluid));
+ }
+
+ @Override
+ public ResourceLocation getCustomModelPath() {
+ return NameConst.MODEL_FLUID_PACKET;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/common/item/ItemPartDualInterface.java b/src/main/java/com/glodblock/github/common/item/ItemPartDualInterface.java
new file mode 100644
index 000000000..18c154fb4
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemPartDualInterface.java
@@ -0,0 +1,37 @@
+package com.glodblock.github.common.item;
+
+import appeng.api.AEApi;
+import appeng.api.parts.IPartItem;
+import com.glodblock.github.common.part.PartDualInterface;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumActionResult;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class ItemPartDualInterface extends Item implements IPartItem {
+
+ public ItemPartDualInterface() {
+ this.setMaxStackSize(64);
+ }
+
+ @Nullable
+ @Override
+ public PartDualInterface createPartFromItemStack(ItemStack is) {
+ return new PartDualInterface(is);
+ }
+
+ @Override
+ @Nonnull
+ public EnumActionResult onItemUse(@Nonnull EntityPlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull EnumHand hand, @Nonnull EnumFacing side,
+ float hitX, float hitY, float hitZ) {
+ return AEApi.instance().partHelper().placeBus(player.getHeldItem(hand), pos, side, player, hand, world);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/common/item/ItemPartFluidPatternTerminal.java b/src/main/java/com/glodblock/github/common/item/ItemPartFluidPatternTerminal.java
new file mode 100644
index 000000000..9d8597a3a
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemPartFluidPatternTerminal.java
@@ -0,0 +1,37 @@
+package com.glodblock.github.common.item;
+
+import appeng.api.AEApi;
+import appeng.api.parts.IPartItem;
+import com.glodblock.github.common.part.PartFluidPatternTerminal;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumActionResult;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class ItemPartFluidPatternTerminal extends Item implements IPartItem {
+
+ public ItemPartFluidPatternTerminal() {
+ this.setMaxStackSize(64);
+ }
+
+ @Nullable
+ @Override
+ public PartFluidPatternTerminal createPartFromItemStack(ItemStack is) {
+ return new PartFluidPatternTerminal(is);
+ }
+
+ @Override
+ @Nonnull
+ public EnumActionResult onItemUse(@Nonnull EntityPlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull EnumHand hand, @Nonnull EnumFacing side,
+ float hitX, float hitY, float hitZ) {
+ return AEApi.instance().partHelper().placeBus(player.getHeldItem(hand), pos, side, player, hand, world);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/part/PartDualInterface.java b/src/main/java/com/glodblock/github/common/part/PartDualInterface.java
new file mode 100644
index 000000000..e4b118b13
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/part/PartDualInterface.java
@@ -0,0 +1,267 @@
+package com.glodblock.github.common.part;
+
+import appeng.api.config.Actionable;
+import appeng.api.config.Upgrades;
+import appeng.api.networking.IGridNode;
+import appeng.api.networking.crafting.ICraftingLink;
+import appeng.api.networking.crafting.ICraftingPatternDetails;
+import appeng.api.networking.crafting.ICraftingProviderHelper;
+import appeng.api.networking.events.MENetworkChannelsChanged;
+import appeng.api.networking.events.MENetworkEventSubscribe;
+import appeng.api.networking.events.MENetworkPowerStatusChange;
+import appeng.api.networking.ticking.IGridTickable;
+import appeng.api.networking.ticking.TickRateModulation;
+import appeng.api.networking.ticking.TickingRequest;
+import appeng.api.parts.IPartCollisionHelper;
+import appeng.api.parts.IPartModel;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.api.util.AECableType;
+import appeng.api.util.IConfigManager;
+import appeng.fluids.helper.DualityFluidInterface;
+import appeng.fluids.helper.IFluidInterfaceHost;
+import appeng.helpers.DualityInterface;
+import appeng.helpers.IInterfaceHost;
+import appeng.helpers.Reflected;
+import appeng.items.parts.PartModels;
+import appeng.parts.PartBasicState;
+import appeng.parts.PartModel;
+import appeng.util.Platform;
+import appeng.util.inv.IAEAppEngInventory;
+import appeng.util.inv.IInventoryDestination;
+import appeng.util.inv.InvOperation;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.common.component.DualityDualInterface;
+import com.glodblock.github.interfaces.FCPriorityHost;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import com.glodblock.github.loader.FCItems;
+import com.google.common.collect.ImmutableSet;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.math.Vec3d;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.EnumSet;
+import java.util.List;
+
+public class PartDualInterface extends PartBasicState
+ implements IGridTickable, IInventoryDestination, IInterfaceHost, IAEAppEngInventory, FCPriorityHost, IFluidInterfaceHost {
+
+ @PartModels
+ public static ResourceLocation[] MODELS = new ResourceLocation[] {
+ new ResourceLocation(FluidCraft.MODID, "part/interface_base"),
+ new ResourceLocation(FluidCraft.MODID, "part/interface_on"),
+ new ResourceLocation(FluidCraft.MODID, "part/interface_off"),
+ new ResourceLocation(FluidCraft.MODID, "part/interface_has_channel")
+ };
+
+ public static final PartModel MODELS_OFF = new PartModel(MODELS[0], MODELS[2]);
+ public static final PartModel MODELS_ON = new PartModel(MODELS[0], MODELS[1]);
+ public static final PartModel MODELS_HAS_CHANNEL = new PartModel(MODELS[0], MODELS[3]);
+
+ private final DualityDualInterface duality = new DualityDualInterface<>(getProxy(), this);
+
+ @Reflected
+ public PartDualInterface(final ItemStack is) {
+ super(is);
+ }
+
+ @MENetworkEventSubscribe
+ public void stateChange(final MENetworkChannelsChanged c) {
+ duality.onChannelStateChange(c);
+ }
+
+ @MENetworkEventSubscribe
+ public void stateChange(final MENetworkPowerStatusChange c) {
+ duality.onPowerStateChange(c);
+ }
+
+ @Override
+ public void getBoxes(final IPartCollisionHelper bch) {
+ bch.addBox(2, 2, 14, 14, 14, 16);
+ bch.addBox(5, 5, 12, 11, 11, 14);
+ }
+
+ @Override
+ public int getInstalledUpgrades(final Upgrades u) {
+ return duality.getInstalledUpgrades(u);
+ }
+
+ @Override
+ public void gridChanged() {
+ duality.onGridChanged();
+ }
+
+ @Override
+ public void readFromNBT(final NBTTagCompound data) {
+ super.readFromNBT(data);
+ duality.readFromNBT(data);
+ }
+
+ @Override
+ public void writeToNBT(final NBTTagCompound data) {
+ super.writeToNBT(data);
+ duality.writeToNBT(data);
+ }
+
+ @Override
+ public void addToWorld() {
+ super.addToWorld();
+ duality.initialize();
+ }
+
+ @Override
+ public void getDrops(final List drops, final boolean wrenched) {
+ duality.addDrops(drops);
+ }
+
+ @Override
+ public float getCableConnectionLength(AECableType cable) {
+ return 4;
+ }
+
+ @Override
+ public IConfigManager getConfigManager() {
+ return duality.getConfigManager();
+ }
+
+ @Override
+ public IItemHandler getInventoryByName(final String name) {
+ return duality.getItemInventoryByName(name);
+ }
+
+ @Override
+ public boolean onPartActivate(final EntityPlayer p, final EnumHand hand, final Vec3d pos) {
+ if (Platform.isServer()) {
+ TileEntity tile = getTileEntity();
+ InventoryHandler.openGui(p, tile.getWorld(), tile.getPos(), getSide().getFacing(), GuiType.DUAL_ITEM_INTERFACE);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean canInsert(final ItemStack stack) {
+ return duality.canInsertItem(stack);
+ }
+
+ @Override
+ @Nonnull
+ public TickingRequest getTickingRequest(@Nonnull final IGridNode node) {
+ return duality.getTickingRequest(node);
+ }
+
+ @Override
+ @Nonnull
+ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) {
+ return duality.onTick(node, ticksSinceLastCall);
+ }
+
+ @Override
+ public void onChangeInventory(final IItemHandler inv, final int slot, final InvOperation mc,
+ final ItemStack removedStack, final ItemStack newStack) {
+ duality.onItemInventoryChange(inv, slot, mc, removedStack, newStack);
+ }
+
+ @Override
+ public DualityInterface getInterfaceDuality() {
+ return duality.getItemInterface();
+ }
+
+ @Override
+ public DualityFluidInterface getDualityFluidInterface() {
+ return duality.getFluidInterface();
+ }
+
+ @Override
+ public EnumSet getTargets() {
+ return EnumSet.of(this.getSide().getFacing());
+ }
+
+ @Override
+ public TileEntity getTileEntity() {
+ return super.getHost().getTile();
+ }
+
+ @Override
+ public boolean pushPattern(final ICraftingPatternDetails patternDetails, final InventoryCrafting table) {
+ return duality.pushPattern(patternDetails, table);
+ }
+
+ @Override
+ public boolean isBusy() {
+ return duality.isCraftingBusy();
+ }
+
+ @Override
+ public void provideCrafting(final ICraftingProviderHelper craftingTracker) {
+ duality.provideCrafting(craftingTracker);
+ }
+
+ @Override
+ public ImmutableSet getRequestedJobs() {
+ return duality.getRequestCraftingJobs();
+ }
+
+ @Override
+ public IAEItemStack injectCraftedItems(final ICraftingLink link, final IAEItemStack items, final Actionable mode) {
+ return duality.injectCraftedItems(link, items, mode);
+ }
+
+ @Override
+ public void jobStateChange(final ICraftingLink link) {
+ duality.onCraftingJobStateChange(link);
+ }
+
+ @Override
+ public int getPriority() {
+ return duality.getPriority();
+ }
+
+ @Override
+ public void setPriority(final int newValue) {
+ duality.setPriority(newValue);
+ }
+
+ @Override
+ public boolean hasCapability(Capability> capabilityClass) {
+ return duality.hasCapability(capabilityClass, getSide().getFacing());
+ }
+
+ @Nullable
+ @Override
+ public T getCapability(Capability capabilityClass) {
+ return duality.getCapability(capabilityClass, getSide().getFacing());
+ }
+
+ @Override
+ public GuiType getGuiType() {
+ return GuiType.DUAL_ITEM_INTERFACE;
+ }
+
+ @Override
+ public ItemStack getItemStackRepresentation() {
+ return new ItemStack(FCItems.PART_DUAL_INTERFACE, 1);
+ }
+
+ @Nonnull
+ @Override
+ public IPartModel getStaticModels() {
+ if (this.isActive() && this.isPowered()) {
+ return MODELS_HAS_CHANNEL;
+ } else if (this.isPowered()) {
+ return MODELS_ON;
+ } else {
+ return MODELS_OFF;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/common/part/PartFluidPatternTerminal.java b/src/main/java/com/glodblock/github/common/part/PartFluidPatternTerminal.java
new file mode 100644
index 000000000..6a0413bf2
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/part/PartFluidPatternTerminal.java
@@ -0,0 +1,96 @@
+package com.glodblock.github.common.part;
+
+import appeng.api.parts.IPartModel;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.core.sync.GuiBridge;
+import appeng.items.parts.PartModels;
+import appeng.parts.PartModel;
+import appeng.parts.reporting.PartPatternTerminal;
+import appeng.tile.inventory.AppEngInternalInventory;
+import appeng.util.Platform;
+import appeng.util.inv.InvOperation;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.common.item.ItemFluidEncodedPattern;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Vec3d;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+
+public class PartFluidPatternTerminal extends PartPatternTerminal {
+
+ @PartModels
+ public static ResourceLocation[] MODELS = new ResourceLocation[] {
+ new ResourceLocation(FluidCraft.MODID, "part/f_pattern_term_on"), // 0
+ new ResourceLocation(FluidCraft.MODID, "part/f_pattern_term_off"), // 1
+ };
+
+ private static final IPartModel MODELS_ON = new PartModel(MODEL_BASE, MODELS[0], MODEL_STATUS_ON);
+ private static final IPartModel MODELS_OFF = new PartModel(MODEL_BASE, MODELS[1], MODEL_STATUS_OFF);
+ private static final IPartModel MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, MODELS[0], MODEL_STATUS_HAS_CHANNEL);
+
+ public PartFluidPatternTerminal(ItemStack is) {
+ super(is);
+ }
+
+ @Nonnull
+ @Override
+ public IPartModel getStaticModels() {
+ return this.selectModel(MODELS_OFF, MODELS_ON, MODELS_HAS_CHANNEL);
+ }
+
+ @Override
+ public boolean onPartActivate(final EntityPlayer player, final EnumHand hand, final Vec3d pos) {
+ TileEntity te = this.getTile();
+ BlockPos tePos = te.getPos();
+ if (Platform.isWrench(player, player.inventory.getCurrentItem(), tePos)) {
+ return super.onPartActivate(player, hand, pos);
+ }
+ if (Platform.isServer()) {
+ if (GuiBridge.GUI_PATTERN_TERMINAL.hasPermissions(te, tePos.getX(), tePos.getY(), tePos.getZ(), getSide(), player)) {
+ InventoryHandler.openGui(player, te.getWorld(), tePos, getSide().getFacing(), GuiType.FLUID_PATTERN_TERMINAL);
+ } else {
+ Platform.openGUI(player, this.getHost().getTile(), this.getSide(), GuiBridge.GUI_ME);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removedStack,
+ ItemStack newStack) {
+ if (slot == 1) {
+ final ItemStack is = inv.getStackInSlot(1);
+ if (!is.isEmpty() && is.getItem() instanceof ItemFluidEncodedPattern) {
+ return;
+ }
+ }
+ super.onChangeInventory(inv, slot, mc, removedStack, newStack);
+ }
+
+ public void onChangeCrafting(IAEItemStack[] newCrafting, IAEItemStack[] newOutput) {
+ IItemHandler crafting = this.getInventoryByName("crafting");
+ IItemHandler output = this.getInventoryByName("output");
+ if (crafting instanceof AppEngInternalInventory && output instanceof AppEngInternalInventory) {
+ for (int x = 0; x < crafting.getSlots() && x < newCrafting.length; x++) {
+ final IAEItemStack item = newCrafting[x];
+ ((AppEngInternalInventory)crafting)
+ .setStackInSlot(x, item == null ? ItemStack.EMPTY : item.createItemStack());
+ }
+
+ for (int x = 0; x < output.getSlots() && x < newOutput.length; x++) {
+ final IAEItemStack item = newOutput[x];
+ ((AppEngInternalInventory)output)
+ .setStackInSlot(x, item == null ? ItemStack.EMPTY : item.createItemStack());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/tile/TileBurette.java b/src/main/java/com/glodblock/github/common/tile/TileBurette.java
new file mode 100644
index 000000000..bf548db40
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileBurette.java
@@ -0,0 +1,84 @@
+package com.glodblock.github.common.tile;
+
+import appeng.fluids.util.AEFluidInventory;
+import appeng.fluids.util.IAEFluidInventory;
+import appeng.fluids.util.IAEFluidTank;
+import appeng.tile.AEBaseInvTile;
+import appeng.tile.inventory.AppEngInternalInventory;
+import appeng.util.inv.InvOperation;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.EnumFacing;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+import net.minecraftforge.items.IItemHandlerModifiable;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class TileBurette extends AEBaseInvTile implements IAEFluidInventory {
+
+ private final AppEngInternalInventory invTarget = new AppEngInternalInventory(this, 1);
+ private final AEFluidInventory invFluid = new AEFluidInventory(this, 1, 8000);
+
+ @Nonnull
+ @Override
+ public IItemHandlerModifiable getInternalInventory() {
+ return invTarget;
+ }
+
+ public IAEFluidTank getFluidInventory() {
+ return invFluid;
+ }
+
+ @Override
+ public boolean canBeRotated() {
+ return false;
+ }
+
+ @Override
+ public boolean hasCapability(Capability> capability, EnumFacing facing) {
+ return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY
+ || capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ @Override
+ public T getCapability(Capability capability, @Nullable EnumFacing facing) {
+ if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
+ return (T)invTarget;
+ } else if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
+ return (T)invFluid;
+ }
+ return null;
+ }
+
+ @Override
+ public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removed, ItemStack added) {
+ // NO-OP
+ }
+
+ @Override
+ public void onFluidInventoryChanged(IAEFluidTank inv, int slot) {
+ saveChanges();
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound data) {
+ super.readFromNBT(data);
+ invTarget.readFromNBT(data, "ItemInv");
+ invFluid.readFromNBT(data, "FluidInv");
+ }
+
+ @Override
+ public NBTTagCompound writeToNBT(NBTTagCompound data) {
+ super.writeToNBT(data);
+ invTarget.writeToNBT(data, "ItemInv");
+ invFluid.writeToNBT(data, "FluidInv");
+ return data;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/tile/TileDualInterface.java b/src/main/java/com/glodblock/github/common/tile/TileDualInterface.java
new file mode 100644
index 000000000..bf7df9813
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileDualInterface.java
@@ -0,0 +1,309 @@
+package com.glodblock.github.common.tile;
+
+import appeng.api.config.Actionable;
+import appeng.api.config.Upgrades;
+import appeng.api.networking.IGridNode;
+import appeng.api.networking.crafting.ICraftingLink;
+import appeng.api.networking.crafting.ICraftingPatternDetails;
+import appeng.api.networking.crafting.ICraftingProviderHelper;
+import appeng.api.networking.events.MENetworkChannelsChanged;
+import appeng.api.networking.events.MENetworkEventSubscribe;
+import appeng.api.networking.events.MENetworkPowerStatusChange;
+import appeng.api.networking.ticking.IGridTickable;
+import appeng.api.networking.ticking.TickRateModulation;
+import appeng.api.networking.ticking.TickingRequest;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.api.util.AECableType;
+import appeng.api.util.AEPartLocation;
+import appeng.api.util.DimensionalCoord;
+import appeng.api.util.IConfigManager;
+import appeng.fluids.helper.DualityFluidInterface;
+import appeng.fluids.helper.IFluidInterfaceHost;
+import appeng.helpers.DualityInterface;
+import appeng.helpers.IInterfaceHost;
+import appeng.tile.grid.AENetworkInvTile;
+import appeng.util.Platform;
+import appeng.util.inv.IInventoryDestination;
+import appeng.util.inv.InvOperation;
+import com.glodblock.github.common.component.DualityDualInterface;
+import com.glodblock.github.interfaces.FCPriorityHost;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.loader.FCBlocks;
+import com.google.common.collect.ImmutableSet;
+import io.netty.buffer.ByteBuf;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.List;
+
+public class TileDualInterface extends AENetworkInvTile
+ implements IGridTickable, IInventoryDestination, IInterfaceHost, FCPriorityHost, IFluidInterfaceHost {
+
+ public TileDualInterface() {
+ super();
+ }
+
+ private final DualityDualInterface duality = new DualityDualInterface<>(getProxy(), this);
+
+ // Indicates that this interface has no specific direction set
+ private boolean omniDirectional = true;
+
+ @MENetworkEventSubscribe
+ public void stateChange(final MENetworkChannelsChanged c) {
+ duality.onChannelStateChange(c);
+ }
+
+ @MENetworkEventSubscribe
+ public void stateChange(final MENetworkPowerStatusChange c) {
+ duality.onPowerStateChange(c);
+ }
+
+ public void setSide(final EnumFacing facing) {
+ if (Platform.isClient()) {
+ return;
+ }
+
+ EnumFacing newForward;
+
+ if (!this.omniDirectional && this.getForward() == facing.getOpposite()) {
+ newForward = facing;
+ } else if (!this.omniDirectional
+ && (this.getForward() == facing || this.getForward() == facing.getOpposite())) {
+ newForward = facing;
+ this.omniDirectional = true;
+ } else if (this.omniDirectional) {
+ newForward = facing.getOpposite();
+ this.omniDirectional = false;
+ } else {
+ newForward = Platform.rotateAround(this.getForward(), facing);
+ }
+
+ if (this.omniDirectional) {
+ this.setOrientation(EnumFacing.NORTH, EnumFacing.UP);
+ } else {
+ EnumFacing newUp = EnumFacing.UP;
+ if (newForward == EnumFacing.UP || newForward == EnumFacing.DOWN) {
+ newUp = EnumFacing.NORTH;
+ }
+ this.setOrientation(newForward, newUp);
+ }
+
+ this.configureNodeSides();
+ this.markForUpdate();
+ this.saveChanges();
+ }
+
+ private void configureNodeSides() {
+ if (this.omniDirectional) {
+ this.getProxy().setValidSides(EnumSet.allOf(EnumFacing.class));
+ } else {
+ this.getProxy().setValidSides(EnumSet.complementOf(EnumSet.of(this.getForward())));
+ }
+ }
+
+ @Override
+ public void getDrops(final World w, final BlockPos pos, final List drops) {
+ duality.addDrops(drops);
+ }
+
+ @Override
+ public void gridChanged() {
+ duality.onGridChanged();
+ }
+
+ @Override
+ public void onReady() {
+ this.configureNodeSides();
+
+ super.onReady();
+ duality.initialize();
+ }
+
+ @Override
+ public NBTTagCompound writeToNBT(final NBTTagCompound data) {
+ super.writeToNBT(data);
+ data.setBoolean("omniDirectional", this.omniDirectional);
+ duality.writeToNBT(data);
+ return data;
+ }
+
+ @Override
+ public void readFromNBT(final NBTTagCompound data) {
+ super.readFromNBT(data);
+ this.omniDirectional = data.getBoolean("omniDirectional");
+ duality.readFromNBT(data);
+ }
+
+ @Override
+ protected boolean readFromStream(final ByteBuf data) throws IOException {
+ final boolean c = super.readFromStream(data);
+ boolean oldOmniDirectional = this.omniDirectional;
+ this.omniDirectional = data.readBoolean();
+ return oldOmniDirectional != this.omniDirectional || c;
+ }
+
+ @Override
+ protected void writeToStream(final ByteBuf data) throws IOException {
+ super.writeToStream(data);
+ data.writeBoolean(this.omniDirectional);
+ }
+
+ @Override
+ @Nonnull
+ public AECableType getCableConnectionType(@Nonnull final AEPartLocation dir) {
+ return AECableType.SMART;
+ }
+
+ @Override
+ public DimensionalCoord getLocation() {
+ return new DimensionalCoord(this.getTileEntity());
+ }
+
+ @Override
+ public boolean canInsert(final ItemStack stack) {
+ return duality.canInsertItem(stack);
+ }
+
+ @Override
+ public IItemHandler getInventoryByName(final String name) {
+ return duality.getItemInventoryByName(name);
+ }
+
+ @Override
+ @Nonnull
+ public TickingRequest getTickingRequest(@Nonnull final IGridNode node) {
+ return duality.getTickingRequest(node);
+ }
+
+ @Override
+ @Nonnull
+ public TickRateModulation tickingRequest(@Nonnull final IGridNode node, final int ticksSinceLastCall) {
+ return duality.onTick(node, ticksSinceLastCall);
+ }
+
+ @Override
+ @Nonnull
+ public IItemHandler getInternalInventory() {
+ return duality.getInternalItemInventory();
+ }
+
+ @Override
+ public void onChangeInventory(final IItemHandler inv, final int slot, final InvOperation mc,
+ final ItemStack removed, final ItemStack added) {
+ duality.onItemInventoryChange(inv, slot, mc, removed, added);
+ }
+
+ @Override
+ public DualityInterface getInterfaceDuality() {
+ return duality.getItemInterface();
+ }
+
+ @Override
+ public DualityFluidInterface getDualityFluidInterface() {
+ return duality.getFluidInterface();
+ }
+
+ @Override
+ public EnumSet getTargets() {
+ if (this.omniDirectional) {
+ return EnumSet.allOf(EnumFacing.class);
+ }
+ return EnumSet.of(this.getForward());
+ }
+
+ @Override
+ public TileEntity getTileEntity() {
+ return this;
+ }
+
+ @Override
+ public IConfigManager getConfigManager() {
+ return duality.getConfigManager();
+ }
+
+ @Override
+ public boolean pushPattern(final ICraftingPatternDetails patternDetails, final InventoryCrafting table) {
+ return duality.pushPattern(patternDetails, table);
+ }
+
+ @Override
+ public boolean isBusy() {
+ return duality.isCraftingBusy();
+ }
+
+ @Override
+ public void provideCrafting(final ICraftingProviderHelper craftingTracker) {
+ duality.provideCrafting(craftingTracker);
+ }
+
+ @Override
+ public int getInstalledUpgrades(final Upgrades u) {
+ return duality.getInstalledUpgrades(u);
+ }
+
+ @Override
+ public ImmutableSet getRequestedJobs() {
+ return duality.getRequestCraftingJobs();
+ }
+
+ @Override
+ public IAEItemStack injectCraftedItems(final ICraftingLink link, final IAEItemStack items, final Actionable mode) {
+ return duality.injectCraftedItems(link, items, mode);
+ }
+
+ @Override
+ public void jobStateChange(final ICraftingLink link) {
+ duality.onCraftingJobStateChange(link);
+ }
+
+ @Override
+ public int getPriority() {
+ return duality.getPriority();
+ }
+
+ @Override
+ public void setPriority(final int newValue) {
+ duality.setPriority(newValue);
+ }
+
+ /**
+ * @return True if this interface is omni-directional.
+ */
+ public boolean isOmniDirectional() {
+ return this.omniDirectional;
+ }
+
+ @Override
+ public boolean hasCapability(Capability> capability, @Nullable EnumFacing facing) {
+ return duality.hasCapability(capability, facing) || super.hasCapability(capability, facing);
+ }
+
+ @Nullable
+ @Override
+ public T getCapability(Capability capability, @Nullable EnumFacing facing) {
+ T capInst = duality.getCapability(capability, facing);
+ return capInst != null ? capInst : super.getCapability(capability, facing);
+ }
+
+ @Override
+ public ItemStack getItemStackRepresentation() {
+ return new ItemStack(FCBlocks.DUAL_INTERFACE);
+ }
+
+ @Override
+ public GuiType getGuiType() {
+ return GuiType.DUAL_ITEM_INTERFACE;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/tile/TileFluidDiscretizer.java b/src/main/java/com/glodblock/github/common/tile/TileFluidDiscretizer.java
new file mode 100644
index 000000000..e8a22e543
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileFluidDiscretizer.java
@@ -0,0 +1,279 @@
+package com.glodblock.github.common.tile;
+
+import appeng.api.AEApi;
+import appeng.api.config.Actionable;
+import appeng.api.networking.GridFlags;
+import appeng.api.networking.crafting.ICraftingGrid;
+import appeng.api.networking.energy.IEnergyGrid;
+import appeng.api.networking.events.MENetworkCellArrayUpdate;
+import appeng.api.networking.events.MENetworkChannelsChanged;
+import appeng.api.networking.events.MENetworkEventSubscribe;
+import appeng.api.networking.events.MENetworkPowerStatusChange;
+import appeng.api.networking.security.IActionSource;
+import appeng.api.networking.storage.IBaseMonitor;
+import appeng.api.networking.storage.IStorageGrid;
+import appeng.api.storage.*;
+import appeng.api.storage.channels.IFluidStorageChannel;
+import appeng.api.storage.channels.IItemStorageChannel;
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.api.storage.data.IItemList;
+import appeng.helpers.Reflected;
+import appeng.me.GridAccessException;
+import appeng.me.cache.CraftingGridCache;
+import appeng.me.helpers.MachineSource;
+import appeng.me.storage.MEInventoryHandler;
+import appeng.tile.grid.AENetworkTile;
+import appeng.util.Platform;
+import com.glodblock.github.common.item.ItemFluidDrop;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TileFluidDiscretizer extends AENetworkTile implements ICellContainer {
+
+ private final FluidDiscretizingInventory fluidDropInv = new FluidDiscretizingInventory();
+ private final FluidCraftingInventory fluidCraftInv = new FluidCraftingInventory();
+ private final IActionSource ownActionSource = new MachineSource(this);
+ private boolean prevActiveState = false;
+
+ @Reflected
+ public TileFluidDiscretizer() {
+ getProxy().setIdlePowerUsage(3D);
+ getProxy().setFlags(GridFlags.REQUIRE_CHANNEL);
+ }
+
+ @Override
+ public boolean canBeRotated() {
+ return false;
+ }
+
+ @Override
+ public int getPriority() {
+ return Integer.MAX_VALUE;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public List getCellArray(IStorageChannel> channel) {
+ if (getProxy().isActive()) {
+ if (channel == AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class)) {
+ return Collections.singletonList(fluidDropInv.invHandler);
+ } else if (channel == AEApi.instance().storage().getStorageChannel(IFluidStorageChannel.class)) {
+ return Collections.singletonList(fluidCraftInv.invHandler);
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void saveChanges(@Nullable ICellInventory> cellInventory) {
+ world.markChunkDirty(pos, this); // optimization, i guess?
+ }
+
+ @Override
+ public void gridChanged() {
+ IMEMonitor fluidGrid = getFluidGrid();
+ if (fluidGrid != null) {
+ fluidGrid.addListener(fluidDropInv, fluidGrid);
+ }
+ }
+
+ @MENetworkEventSubscribe
+ public void onPowerUpdate(MENetworkPowerStatusChange event) {
+ updateState();
+ }
+
+ @MENetworkEventSubscribe
+ public void onChannelUpdate(MENetworkChannelsChanged event) {
+ updateState();
+ }
+
+ private void updateState() {
+ boolean isActive = getProxy().isActive();
+ if (isActive != prevActiveState) {
+ prevActiveState = isActive;
+ try {
+ getProxy().getGrid().postEvent(new MENetworkCellArrayUpdate());
+ } catch (GridAccessException e) {
+ // NO-OP
+ }
+ }
+ }
+
+ @Override
+ public void blinkCell(int slot) {
+ // NO-OP
+ }
+
+ @Nullable
+ private IEnergyGrid getEnergyGrid() {
+ try {
+ return getProxy().getGrid().getCache(IEnergyGrid.class);
+ } catch (GridAccessException e) {
+ return null;
+ }
+ }
+
+ @Nullable
+ private IMEMonitor getFluidGrid() {
+ try {
+ return getProxy().getGrid().getCache(IStorageGrid.class)
+ .getInventory(AEApi.instance().storage().getStorageChannel(IFluidStorageChannel.class));
+ } catch (GridAccessException e) {
+ return null;
+ }
+ }
+
+ private class FluidDiscretizingInventory implements IMEInventory, IMEMonitorHandlerReceiver {
+
+ private final MEInventoryHandler invHandler = new MEInventoryHandler<>(this, getChannel());
+ @Nullable
+ private List itemCache = null;
+
+ FluidDiscretizingInventory() {
+ invHandler.setPriority(Integer.MAX_VALUE);
+ }
+
+ @SuppressWarnings("DuplicatedCode")
+ @Nullable
+ @Override
+ public IAEItemStack extractItems(IAEItemStack request, Actionable mode, IActionSource src) {
+ IAEFluidStack fluidStack = ItemFluidDrop.getAeFluidStack(request);
+ if (fluidStack == null) {
+ return null;
+ }
+ IMEMonitor fluidGrid = getFluidGrid();
+ if (fluidGrid == null) {
+ return null;
+ }
+ IEnergyGrid energyGrid = getEnergyGrid();
+ if (energyGrid == null) {
+ return null;
+ }
+ return ItemFluidDrop.newAeStack(Platform.poweredExtraction(energyGrid, fluidGrid, fluidStack, ownActionSource, mode));
+ }
+
+ @SuppressWarnings("DuplicatedCode")
+ @Nullable
+ @Override
+ public IAEItemStack injectItems(IAEItemStack input, Actionable type, IActionSource src) {
+ IAEFluidStack fluidStack = ItemFluidDrop.getAeFluidStack(input);
+ if (fluidStack == null) {
+ return input;
+ }
+ IMEMonitor fluidGrid = getFluidGrid();
+ if (fluidGrid == null) {
+ return input;
+ }
+ IEnergyGrid energyGrid = getEnergyGrid();
+ if (energyGrid == null) {
+ return input;
+ }
+ return ItemFluidDrop.newAeStack(Platform.poweredInsert(energyGrid, fluidGrid, fluidStack, ownActionSource, type));
+ }
+
+ @Override
+ public IItemList getAvailableItems(IItemList out) {
+ if (itemCache == null) {
+ itemCache = new ArrayList<>();
+ IMEMonitor fluidGrid = getFluidGrid();
+ if (fluidGrid != null) {
+ for (IAEFluidStack fluid : fluidGrid.getStorageList()) {
+ IAEItemStack stack = ItemFluidDrop.newAeStack(fluid);
+ if (stack != null) {
+ itemCache.add(stack);
+ }
+ }
+ }
+ }
+ for (IAEItemStack stack : itemCache) {
+ out.addStorage(stack);
+ }
+ return out;
+ }
+
+ @Override
+ public boolean isValid(Object verificationToken) {
+ IMEMonitor fluidGrid = getFluidGrid();
+ return fluidGrid != null && fluidGrid == verificationToken;
+ }
+
+ @Override
+ public void postChange(IBaseMonitor monitor, Iterable change, IActionSource actionSource) {
+ itemCache = null;
+ try {
+ List mappedChanges = new ArrayList<>();
+ for (IAEFluidStack fluidStack : change) {
+ IAEItemStack itemStack = ItemFluidDrop.newAeStack(fluidStack);
+ if (itemStack != null) {
+ mappedChanges.add(itemStack);
+ }
+ }
+ getProxy().getGrid().getCache(IStorageGrid.class)
+ .postAlterationOfStoredItems(getChannel(), mappedChanges, ownActionSource);
+ } catch (GridAccessException e) {
+ // NO-OP
+ }
+ }
+
+ @Override
+ public void onListUpdate() {
+ // NO-OP
+ }
+
+ @Override
+ public IStorageChannel getChannel() {
+ return AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class);
+ }
+
+ }
+
+ private class FluidCraftingInventory implements IMEInventory {
+
+ private final MEInventoryHandler invHandler = new MEInventoryHandler<>(this, getChannel());
+
+ FluidCraftingInventory() {
+ invHandler.setPriority(Integer.MAX_VALUE);
+ }
+
+ @Nullable
+ @Override
+ public IAEFluidStack injectItems(IAEFluidStack input, Actionable type, IActionSource src) {
+ ICraftingGrid craftingGrid;
+ try {
+ craftingGrid = getProxy().getGrid().getCache(ICraftingGrid.class);
+ } catch (GridAccessException e) {
+ return null;
+ }
+ if (craftingGrid instanceof CraftingGridCache) {
+ IAEItemStack remaining = ((CraftingGridCache)craftingGrid).injectItems(
+ ItemFluidDrop.newAeStack(input), type, ownActionSource);
+ if (remaining != null) {
+ return ItemFluidDrop.getAeFluidStack(remaining);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public IAEFluidStack extractItems(IAEFluidStack request, Actionable mode, IActionSource src) {
+ return null;
+ }
+
+ @Override
+ public IItemList getAvailableItems(IItemList out) {
+ return out;
+ }
+
+ @Override
+ public IStorageChannel getChannel() {
+ return AEApi.instance().storage().getStorageChannel(IFluidStorageChannel.class);
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/tile/TileFluidPacketDecoder.java b/src/main/java/com/glodblock/github/common/tile/TileFluidPacketDecoder.java
new file mode 100644
index 000000000..dcec9b7bf
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileFluidPacketDecoder.java
@@ -0,0 +1,130 @@
+package com.glodblock.github.common.tile;
+
+import appeng.api.AEApi;
+import appeng.api.networking.GridFlags;
+import appeng.api.networking.IGridNode;
+import appeng.api.networking.energy.IEnergyGrid;
+import appeng.api.networking.security.IActionSource;
+import appeng.api.networking.storage.IStorageGrid;
+import appeng.api.networking.ticking.IGridTickable;
+import appeng.api.networking.ticking.TickRateModulation;
+import appeng.api.networking.ticking.TickingRequest;
+import appeng.api.storage.IMEMonitor;
+import appeng.api.storage.channels.IFluidStorageChannel;
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.fluids.util.AEFluidStack;
+import appeng.helpers.Reflected;
+import appeng.me.GridAccessException;
+import appeng.me.helpers.MachineSource;
+import appeng.tile.grid.AENetworkTile;
+import appeng.tile.inventory.AppEngInternalInventory;
+import appeng.util.Platform;
+import appeng.util.inv.IAEAppEngInventory;
+import appeng.util.inv.InvOperation;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.EnumFacing;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+import net.minecraftforge.items.IItemHandlerModifiable;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class TileFluidPacketDecoder extends AENetworkTile implements IGridTickable, IAEAppEngInventory {
+
+ private final AppEngInternalInventory inventory = new AppEngInternalInventory(this, 1);
+ private final IActionSource ownActionSource = new MachineSource(this);
+
+ @Reflected
+ public TileFluidPacketDecoder() {
+ getProxy().setIdlePowerUsage(1D);
+ getProxy().setFlags(GridFlags.REQUIRE_CHANNEL);
+ }
+
+ public IItemHandlerModifiable getInventory() {
+ return inventory;
+ }
+
+ @Override
+ public boolean canBeRotated() {
+ return false;
+ }
+
+ @Override
+ public boolean hasCapability(@Nonnull Capability> capability, @Nullable EnumFacing facing) {
+ return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ @Override
+ public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) {
+ if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
+ return (T)inventory;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ @Nonnull
+ public TickingRequest getTickingRequest(@Nonnull IGridNode node) {
+ return new TickingRequest(5, 120, false, true);
+ }
+
+ @Override
+ @Nonnull
+ public TickRateModulation tickingRequest(@Nonnull IGridNode node, int ticksSinceLastCall) {
+ ItemStack stack = inventory.getStackInSlot(0);
+ if (stack.isEmpty() || !(stack.getItem() instanceof ItemFluidPacket)) {
+ return TickRateModulation.SLEEP;
+ }
+ FluidStack fluid = ItemFluidPacket.getFluidStack(stack);
+ if (fluid == null || fluid.amount <= 0) {
+ inventory.setStackInSlot(0, ItemStack.EMPTY);
+ return TickRateModulation.SLEEP;
+ }
+ IAEFluidStack aeFluid = AEFluidStack.fromFluidStack(fluid);
+ IEnergyGrid energyGrid = node.getGrid().getCache(IEnergyGrid.class);
+ IMEMonitor fluidGrid = node.getGrid().getCache(IStorageGrid.class)
+ .getInventory(AEApi.instance().storage().getStorageChannel(IFluidStorageChannel.class));
+ IAEFluidStack remaining = Platform.poweredInsert(energyGrid, fluidGrid, aeFluid, ownActionSource);
+ if (remaining != null) {
+ if (remaining.getStackSize() == aeFluid.getStackSize()) {
+ return TickRateModulation.SLOWER;
+ }
+ inventory.setStackInSlot(0, ItemFluidPacket.newStack(remaining.getFluidStack()));
+ return TickRateModulation.FASTER;
+ } else {
+ inventory.setStackInSlot(0, ItemStack.EMPTY);
+ return TickRateModulation.SLEEP;
+ }
+ }
+
+ @Override
+ public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removedStack, ItemStack newStack) {
+ try {
+ getProxy().getTick().alertDevice(getProxy().getNode());
+ } catch (GridAccessException e) {
+ // NO-OP
+ }
+ }
+
+ @Override
+ public NBTTagCompound writeToNBT(NBTTagCompound data) {
+ super.writeToNBT(data);
+ inventory.writeToNBT(data, "Inventory");
+ return data;
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound data) {
+ super.readFromNBT(data);
+ inventory.readFromNBT(data, "Inventory");
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/tile/TileFluidPatternEncoder.java b/src/main/java/com/glodblock/github/common/tile/TileFluidPatternEncoder.java
new file mode 100644
index 000000000..b8e8d1624
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileFluidPatternEncoder.java
@@ -0,0 +1,98 @@
+package com.glodblock.github.common.tile;
+
+import appeng.api.AEApi;
+import appeng.api.storage.channels.IItemStorageChannel;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.tile.AEBaseTile;
+import appeng.tile.inventory.AppEngInternalInventory;
+import appeng.util.inv.IAEAppEngInventory;
+import appeng.util.inv.InvOperation;
+import com.glodblock.github.interfaces.AeStackInventory;
+import com.glodblock.github.inventory.AeStackInventoryImpl;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+import net.minecraftforge.items.IItemHandlerModifiable;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+
+public class TileFluidPatternEncoder extends AEBaseTile implements IAEAppEngInventory {
+
+ private final AppEngInternalInventory patternInv = new AppEngInternalInventory(this, 2);
+ private final AeStackInventoryImpl crafting = new AeStackInventoryImpl<>(
+ AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class), 9, this);
+ private final AeStackInventoryImpl output = new AeStackInventoryImpl<>(
+ AEApi.instance().storage().getStorageChannel(IItemStorageChannel.class), 3, this);
+
+ public IItemHandlerModifiable getInventory() {
+ return patternInv;
+ }
+
+ public AeStackInventory getCraftingSlots() {
+ return crafting;
+ }
+
+ public AeStackInventory getOutputSlots() {
+ return output;
+ }
+
+ @Override
+ public boolean canBeRotated() {
+ return false;
+ }
+
+ @Override
+ public boolean hasCapability(@Nonnull Capability> capability, @Nullable EnumFacing facing) {
+ return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ @Override
+ public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) {
+ if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
+ return (T)patternInv;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removedStack, ItemStack newStack) {
+ // NO-OP
+ }
+
+ @Override
+ public void getDrops(World world, BlockPos pos, List drops) {
+ for (ItemStack stack : patternInv) {
+ if (!stack.isEmpty()) {
+ drops.add(stack);
+ }
+ }
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound data) {
+ super.readFromNBT(data);
+ patternInv.readFromNBT(data, "Inventory");
+ crafting.readFromNbt(data, "CraftingSlots");
+ output.readFromNbt(data, "OutputSlots");
+ }
+
+ @Override
+ public NBTTagCompound writeToNBT(NBTTagCompound data) {
+ super.writeToNBT(data);
+ patternInv.writeToNBT(data, "Inventory");
+ crafting.writeToNbt(data, "CraftingSlots");
+ output.writeToNbt(data, "OutputSlots");
+ return data;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/common/tile/TileIngredientBuffer.java b/src/main/java/com/glodblock/github/common/tile/TileIngredientBuffer.java
new file mode 100644
index 000000000..225f8a286
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileIngredientBuffer.java
@@ -0,0 +1,139 @@
+package com.glodblock.github.common.tile;
+
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.fluids.util.AEFluidInventory;
+import appeng.fluids.util.AEFluidStack;
+import appeng.fluids.util.IAEFluidInventory;
+import appeng.fluids.util.IAEFluidTank;
+import appeng.tile.AEBaseInvTile;
+import appeng.tile.inventory.AppEngInternalInventory;
+import appeng.util.inv.InvOperation;
+import io.netty.buffer.ByteBuf;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.EnumFacing;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
+import net.minecraftforge.fml.common.network.ByteBufUtils;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+
+public class TileIngredientBuffer extends AEBaseInvTile implements IAEFluidInventory {
+
+ private final AppEngInternalInventory invItems = new AppEngInternalInventory(this, 9);
+ private final AEFluidInventory invFluids = new AEFluidInventory(this, 4, 16000);
+
+ @Nonnull
+ @Override
+ public IItemHandler getInternalInventory() {
+ return invItems;
+ }
+
+ public IAEFluidTank getFluidInventory() {
+ return invFluids;
+ }
+
+ @Override
+ public boolean canBeRotated() {
+ return false;
+ }
+
+ @Override
+ public boolean hasCapability(Capability> capability, EnumFacing facing) {
+ return capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY
+ || capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ @Override
+ public T getCapability(Capability capability, @Nullable EnumFacing facing) {
+ if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
+ return (T)invItems;
+ } else if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
+ return (T)invFluids;
+ }
+ return null;
+ }
+
+ @Override
+ public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removed, ItemStack added) {
+ markForUpdate();
+ }
+
+ @Override
+ public void onFluidInventoryChanged(IAEFluidTank inv, int slot) {
+ saveChanges();
+ markForUpdate();
+ }
+
+ @Override
+ protected void writeToStream(ByteBuf data) throws IOException {
+ super.writeToStream(data);
+ for (int i = 0; i < invItems.getSlots(); i++) {
+ ByteBufUtils.writeItemStack(data, invItems.getStackInSlot(i));
+ }
+ int fluidMask = 0;
+ for (int i = 0; i < invFluids.getSlots(); i++) {
+ if (invFluids.getFluidInSlot(i) != null) {
+ fluidMask |= 1 << i;
+ }
+ }
+ data.writeByte(fluidMask);
+ for (int i = 0; i < invFluids.getSlots(); i++) {
+ IAEFluidStack fluid = invFluids.getFluidInSlot(i);
+ if (fluid != null) {
+ fluid.writeToPacket(data);
+ }
+ }
+ }
+
+ @Override
+ protected boolean readFromStream(ByteBuf data) throws IOException {
+ boolean changed = super.readFromStream(data);
+ for (int i = 0; i < invItems.getSlots(); i++) {
+ ItemStack stack = ByteBufUtils.readItemStack(data);
+ if (!ItemStack.areItemStacksEqual(stack, invItems.getStackInSlot(i))) {
+ invItems.setStackInSlot(i, stack);
+ changed = true;
+ }
+ }
+ int fluidMask = data.readByte();
+ for (int i = 0; i < invFluids.getSlots(); i++) {
+ if ((fluidMask & (1 << i)) != 0) {
+ IAEFluidStack fluid = AEFluidStack.fromPacket(data);
+ if (fluid != null) { // this shouldn't happen, but better safe than sorry
+ IAEFluidStack origFluid = invFluids.getFluidInSlot(i);
+ if (!fluid.equals(origFluid) || fluid.getStackSize() != origFluid.getStackSize()) {
+ invFluids.setFluidInSlot(i, fluid);
+ changed = true;
+ }
+ }
+ } else if (invFluids.getFluidInSlot(i) != null) {
+ invFluids.setFluidInSlot(i, null);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound data) {
+ super.readFromNBT(data);
+ invItems.readFromNBT(data, "ItemInv");
+ invFluids.readFromNBT(data, "FluidInv");
+ }
+
+ @Override
+ public NBTTagCompound writeToNBT(NBTTagCompound data) {
+ super.writeToNBT(data);
+ invItems.writeToNBT(data, "ItemInv");
+ invFluids.writeToNBT(data, "FluidInv");
+ return data;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/coremod/CoreModHooks.java b/src/main/java/com/glodblock/github/coremod/CoreModHooks.java
new file mode 100644
index 000000000..b14ef6786
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coremod/CoreModHooks.java
@@ -0,0 +1,105 @@
+package com.glodblock.github.coremod;
+
+import appeng.api.networking.IGrid;
+import appeng.api.networking.IGridHost;
+import appeng.api.networking.IGridNode;
+import appeng.api.networking.IMachineSet;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.me.MachineSet;
+import appeng.parts.misc.PartInterface;
+import appeng.tile.misc.TileInterface;
+import appeng.util.InventoryAdaptor;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.common.part.PartDualInterface;
+import com.glodblock.github.common.tile.TileDualInterface;
+import com.glodblock.github.handler.FluidConvertingItemHandler;
+import com.glodblock.github.inventory.FluidConvertingInventoryAdaptor;
+import com.glodblock.github.inventory.FluidConvertingInventoryCrafting;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.util.Ae2Reflect;
+import com.glodblock.github.util.SetBackedMachineSet;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumFacing;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.common.capabilities.ICapabilityProvider;
+import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nullable;
+import java.util.HashSet;
+import java.util.Set;
+import com.google.common.collect.Sets;
+
+public class CoreModHooks {
+
+ public static InventoryCrafting wrapCraftingBuffer(InventoryCrafting inv) {
+ return new FluidConvertingInventoryCrafting(Ae2Reflect.getCraftContainer(inv), inv.getWidth(), inv.getHeight());
+ }
+
+ public static IAEItemStack wrapFluidPacketStack(IAEItemStack stack) {
+ if (stack.getItem() == FCItems.FLUID_PACKET) {
+ IAEItemStack dropStack = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stack.getDefinition()));
+ if (dropStack != null) {
+ return dropStack;
+ }
+ }
+ return stack;
+ }
+
+ @Nullable
+ public static InventoryAdaptor wrapInventory(@Nullable TileEntity tile, EnumFacing face) {
+ return tile != null ? FluidConvertingInventoryAdaptor.wrap(tile, face) : null;
+ }
+
+ public static long getCraftingByteCost(IAEItemStack stack) {
+ return stack.getItem() instanceof ItemFluidDrop
+ ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize();
+ }
+
+ public static boolean checkForItemHandler(ICapabilityProvider capProvider, Capability> capability, EnumFacing side) {
+ return capProvider.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, side)
+ || capProvider.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, side);
+ }
+
+ public static IItemHandler wrapItemHandler(ICapabilityProvider capProvider, Capability> capability, EnumFacing side) {
+ return FluidConvertingItemHandler.wrap(capProvider, side);
+ }
+
+ public static IAEItemStack[] flattenFluidPackets(IAEItemStack[] stacks) {
+ for (int i = 0; i < stacks.length; i++) {
+ if (stacks[i].getItem() instanceof ItemFluidPacket) {
+ stacks[i] = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stacks[i]));
+ }
+ }
+ return stacks;
+ }
+
+ public static IMachineSet getMachines(IGrid grid, Class extends IGridHost> c) {
+ if (c == TileInterface.class) {
+ return unionMachineSets(grid.getMachines(c), grid.getMachines(TileDualInterface.class));
+ } else if (c == PartInterface.class) {
+ return unionMachineSets(grid.getMachines(c), grid.getMachines(PartDualInterface.class));
+ } else {
+ return grid.getMachines(c);
+ }
+ }
+
+ private static IMachineSet unionMachineSets(IMachineSet a, IMachineSet b) {
+ if (a.isEmpty()) {
+ return b;
+ } else if (b.isEmpty()) {
+ return a;
+ } else if (a instanceof MachineSet && b instanceof MachineSet) {
+ return new SetBackedMachineSet(TileInterface.class, Sets.union((MachineSet)a, (MachineSet)b));
+ } else {
+ Set union = new HashSet<>();
+ a.forEach(union::add);
+ b.forEach(union::add);
+ return new SetBackedMachineSet(TileInterface.class, union);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java b/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java
new file mode 100644
index 000000000..2a681268c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coremod/FCClassTransformer.java
@@ -0,0 +1,67 @@
+package com.glodblock.github.coremod;
+
+import com.glodblock.github.coremod.transform.*;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import net.minecraft.launchwrapper.IClassTransformer;
+import org.objectweb.asm.ClassWriter;
+
+public class FCClassTransformer implements IClassTransformer {
+
+ @Override
+ public byte[] transform(String name, String transformedName, byte[] code) {
+ Transform tform;
+ switch (transformedName) {
+ case "appeng.crafting.CraftingTreeNode":
+ tform = CraftingTreeNodeTransformer.INSTANCE;
+ break;
+ case "appeng.helpers.DualityInterface":
+ tform = DualityInterfaceTransformer.INSTANCE;
+ break;
+ case "appeng.me.cluster.implementations.CraftingCPUCluster":
+ tform = CraftingCpuTransformer.INSTANCE;
+ break;
+ case "thelm.packagedauto.integration.appeng.recipe.PackageCraftingPatternHelper":
+ tform = PautoCraftingPatternHelperTransformer.TFORM_INPUTS;
+ break;
+ case "thelm.packagedauto.integration.appeng.recipe.RecipeCraftingPatternHelper":
+ tform = PautoCraftingPatternHelperTransformer.TFORM_OUTPUTS;
+ break;
+ case "thelm.packagedauto.tile.TileUnpackager":
+ tform = TileUnpackagerTransformer.INSTANCE;
+ break;
+ case "appeng.container.implementations.ContainerInterfaceTerminal":
+ tform = ContainerInterfaceTerminalTransformer.INSTANCE;
+ break;
+ default:
+ return code;
+ }
+ System.out.println("[AE2FC] Transforming class: " + transformedName);
+ return tform.transformClass(code);
+ }
+
+ public interface Transform {
+
+ byte[] transformClass(byte[] code);
+
+ }
+
+ public static abstract class ClassMapper implements Transform {
+
+ @Override
+ public byte[] transformClass(byte[] code) {
+ ClassReader reader = new ClassReader(code);
+ ClassWriter writer = new ClassWriter(reader, getWriteFlags());
+ reader.accept(getClassMapper(writer), 0);
+ return writer.toByteArray();
+ }
+
+ protected int getWriteFlags() {
+ return 0;
+ }
+
+ protected abstract ClassVisitor getClassMapper(ClassVisitor downstream);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/coremod/FCCoreMod.java b/src/main/java/com/glodblock/github/coremod/FCCoreMod.java
new file mode 100644
index 000000000..789f05fc2
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coremod/FCCoreMod.java
@@ -0,0 +1,40 @@
+package com.glodblock.github.coremod;
+
+import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+
+@IFMLLoadingPlugin.MCVersion("1.12.2")
+@IFMLLoadingPlugin.TransformerExclusions("com.glodblock.github.coremod")
+public class FCCoreMod implements IFMLLoadingPlugin {
+
+ @Override
+ public String[] getASMTransformerClass() {
+ return new String[] { FCCoreMod.class.getPackage().getName() + ".FCClassTransformer" };
+ }
+
+ @Nullable
+ @Override
+ public String getModContainerClass() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getSetupClass() {
+ return null;
+ }
+
+ @Override
+ public void injectData(Map data) {
+ // NO-OP
+ }
+
+ @Nullable
+ @Override
+ public String getAccessTransformerClass() {
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/coremod/transform/ContainerInterfaceTerminalTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/ContainerInterfaceTerminalTransformer.java
new file mode 100644
index 000000000..5d6c82e1a
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coremod/transform/ContainerInterfaceTerminalTransformer.java
@@ -0,0 +1,58 @@
+package com.glodblock.github.coremod.transform;
+
+import com.glodblock.github.coremod.FCClassTransformer;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class ContainerInterfaceTerminalTransformer extends FCClassTransformer.ClassMapper {
+
+ public static final ContainerInterfaceTerminalTransformer INSTANCE = new ContainerInterfaceTerminalTransformer();
+
+ private ContainerInterfaceTerminalTransformer() {
+ // NO-OP
+ }
+
+ @Override
+ protected ClassVisitor getClassMapper(ClassVisitor downstream) {
+ return new TransformContainerInterfaceTerminal(Opcodes.ASM5, downstream);
+ }
+
+ private static class TransformContainerInterfaceTerminal extends ClassVisitor {
+
+ TransformContainerInterfaceTerminal(int api, ClassVisitor cv) {
+ super(api, cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ if (name.equals("detectAndSendChanges") || name.equals("func_75142_b") || name.equals("regenList")) {
+ return new TransformDetectAndSendChanges(api, super.visitMethod(access, name, desc, signature, exceptions));
+ }
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ }
+
+ private static class TransformDetectAndSendChanges extends MethodVisitor {
+
+ TransformDetectAndSendChanges(int api, MethodVisitor mv) {
+ super(api, mv);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ if (opcode == Opcodes.INVOKEINTERFACE
+ && owner.equals("appeng/api/networking/IGrid") && name.equals("getMachines")) {
+ super.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/glodblock/github/coremod/CoreModHooks",
+ "getMachines",
+ "(Lappeng/api/networking/IGrid;Ljava/lang/Class;)Lappeng/api/networking/IMachineSet;",
+ false);
+ } else {
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/coremod/transform/CraftingCpuTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/CraftingCpuTransformer.java
new file mode 100644
index 000000000..c98e7f7b6
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coremod/transform/CraftingCpuTransformer.java
@@ -0,0 +1,86 @@
+package com.glodblock.github.coremod.transform;
+
+import com.glodblock.github.coremod.FCClassTransformer;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CraftingCpuTransformer extends FCClassTransformer.ClassMapper {
+
+ public static final CraftingCpuTransformer INSTANCE = new CraftingCpuTransformer();
+
+ private CraftingCpuTransformer() {
+ // NO-OP
+ }
+
+ @Override
+ protected ClassVisitor getClassMapper(ClassVisitor downstream) {
+ return new TransformCraftingCPUCluster(Opcodes.ASM5, downstream);
+ }
+
+ private static class TransformCraftingCPUCluster extends ClassVisitor {
+
+ TransformCraftingCPUCluster(int api, ClassVisitor cv) {
+ super(api, cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ if (name.equals("executeCrafting")) {
+ return new TransformExecuteCrafting(api, super.visitMethod(access, name, desc, signature, exceptions));
+ }
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ }
+
+ private static class TransformExecuteCrafting extends MethodVisitor {
+
+ private boolean gotInventory = false;
+
+ TransformExecuteCrafting(int api, MethodVisitor mv) {
+ super(api, mv);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (opcode == Opcodes.GETFIELD
+ && owner.equals("appeng/me/cluster/implementations/CraftingCPUCluster") && name.equals("inventory")) {
+ gotInventory = true;
+ }
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ gotInventory = false;
+ super.visitLineNumber(line, start);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ if (gotInventory) {
+ if (opcode == Opcodes.INVOKESTATIC
+ && owner.equals("appeng/util/item/AEItemStack") && name.equals("fromItemStack")) {
+ gotInventory = false;
+ super.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/glodblock/github/coremod/CoreModHooks",
+ "wrapFluidPacketStack",
+ "(Lappeng/api/storage/data/IAEItemStack;)Lappeng/api/storage/data/IAEItemStack;",
+ false);
+ }
+ } else if (opcode == Opcodes.INVOKESPECIAL
+ && owner.equals("net/minecraft/inventory/InventoryCrafting") && name.equals("")) {
+ super.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/glodblock/github/coremod/CoreModHooks",
+ "wrapCraftingBuffer",
+ "(Lnet/minecraft/inventory/InventoryCrafting;)Lnet/minecraft/inventory/InventoryCrafting;",
+ false);
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/coremod/transform/CraftingTreeNodeTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/CraftingTreeNodeTransformer.java
new file mode 100644
index 000000000..c4028f3c2
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coremod/transform/CraftingTreeNodeTransformer.java
@@ -0,0 +1,77 @@
+package com.glodblock.github.coremod.transform;
+
+import com.glodblock.github.coremod.FCClassTransformer;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CraftingTreeNodeTransformer extends FCClassTransformer.ClassMapper {
+
+ public static final CraftingTreeNodeTransformer INSTANCE = new CraftingTreeNodeTransformer();
+
+ private CraftingTreeNodeTransformer() {
+ // NO-OP
+ }
+
+ @Override
+ protected ClassVisitor getClassMapper(ClassVisitor downstream) {
+ return new TransformCraftingTreeNode(Opcodes.ASM5, downstream);
+ }
+
+ private static class TransformCraftingTreeNode extends ClassVisitor {
+
+ TransformCraftingTreeNode(int api, ClassVisitor cv) {
+ super(api, cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ if (name.equals("request")) {
+ return new TransformRequest(api, super.visitMethod(access, name, desc, signature, exceptions));
+ }
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ }
+
+ private static class TransformRequest extends MethodVisitor {
+
+ private boolean writingBytes = false;
+
+ TransformRequest(int api, MethodVisitor mv) {
+ super(api, mv);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (opcode == Opcodes.GETFIELD && owner.equals("appeng/crafting/CraftingTreeNode") && name.equals("bytes")) {
+ writingBytes = true;
+ }
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ writingBytes = false; // no write here
+ super.visitLineNumber(line, start);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ if (writingBytes && opcode == Opcodes.INVOKEINTERFACE
+ && owner.equals("appeng/api/storage/data/IAEItemStack") && name.equals("getStackSize")) {
+ writingBytes = false;
+ super.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/glodblock/github/coremod/CoreModHooks",
+ "getCraftingByteCost",
+ "(Lappeng/api/storage/data/IAEItemStack;)J",
+ false);
+ } else {
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/coremod/transform/DualityInterfaceTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/DualityInterfaceTransformer.java
new file mode 100644
index 000000000..7db0adabd
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coremod/transform/DualityInterfaceTransformer.java
@@ -0,0 +1,62 @@
+package com.glodblock.github.coremod.transform;
+
+import com.glodblock.github.coremod.FCClassTransformer;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class DualityInterfaceTransformer extends FCClassTransformer.ClassMapper {
+
+ public static final DualityInterfaceTransformer INSTANCE = new DualityInterfaceTransformer();
+
+ private DualityInterfaceTransformer() {
+ // NO-OP
+ }
+
+ @Override
+ protected ClassVisitor getClassMapper(ClassVisitor downstream) {
+ return new TransformDualityInterface(Opcodes.ASM5, downstream);
+ }
+
+ private static class TransformDualityInterface extends ClassVisitor {
+
+ TransformDualityInterface(int api, ClassVisitor cv) {
+ super(api, cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ switch (name) {
+ case "pushItemsOut":
+ case "pushPattern":
+ case "isBusy":
+ return new TransformInvAdaptorCalls(api, super.visitMethod(access, name, desc, signature, exceptions));
+ default:
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ }
+
+ }
+
+ private static class TransformInvAdaptorCalls extends MethodVisitor {
+
+ TransformInvAdaptorCalls(int api, MethodVisitor mv) {
+ super(api, mv);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ if (opcode == Opcodes.INVOKESTATIC && owner.equals("appeng/util/InventoryAdaptor") && name.equals("getAdaptor")) {
+ super.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/glodblock/github/coremod/CoreModHooks",
+ "wrapInventory",
+ "(Lnet/minecraft/tileentity/TileEntity;Lnet/minecraft/util/EnumFacing;)Lappeng/util/InventoryAdaptor;",
+ false);
+ } else {
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/coremod/transform/PautoCraftingPatternHelperTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/PautoCraftingPatternHelperTransformer.java
new file mode 100644
index 000000000..22be04773
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coremod/transform/PautoCraftingPatternHelperTransformer.java
@@ -0,0 +1,57 @@
+package com.glodblock.github.coremod.transform;
+
+import com.glodblock.github.coremod.FCClassTransformer;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class PautoCraftingPatternHelperTransformer extends FCClassTransformer.ClassMapper {
+
+ public static final PautoCraftingPatternHelperTransformer TFORM_INPUTS = new PautoCraftingPatternHelperTransformer("inputs");
+ public static final PautoCraftingPatternHelperTransformer TFORM_OUTPUTS = new PautoCraftingPatternHelperTransformer("outputs");
+
+ private final String fieldName;
+
+ private PautoCraftingPatternHelperTransformer(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ protected ClassVisitor getClassMapper(ClassVisitor downstream) {
+ return new TransformPackageCraftingPatternHelper(Opcodes.ASM5, downstream);
+ }
+
+ private class TransformPackageCraftingPatternHelper extends ClassVisitor {
+
+ TransformPackageCraftingPatternHelper(int api, ClassVisitor cv) {
+ super(api, cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ if (name.equals("")) {
+ return new TransformCtor(api, super.visitMethod(access, name, desc, signature, exceptions));
+ }
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ }
+
+ private class TransformCtor extends MethodVisitor {
+
+ TransformCtor(int api, MethodVisitor mv) {
+ super(api, mv);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (opcode == Opcodes.PUTFIELD && name.equals(fieldName) && desc.equals("[Lappeng/api/storage/data/IAEItemStack;")) {
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/glodblock/github/coremod/CoreModHooks", "flattenFluidPackets",
+ "([Lappeng/api/storage/data/IAEItemStack;)[Lappeng/api/storage/data/IAEItemStack;", false);
+ }
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/coremod/transform/TileUnpackagerTransformer.java b/src/main/java/com/glodblock/github/coremod/transform/TileUnpackagerTransformer.java
new file mode 100644
index 000000000..bf3b32708
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coremod/transform/TileUnpackagerTransformer.java
@@ -0,0 +1,84 @@
+package com.glodblock.github.coremod.transform;
+
+import com.glodblock.github.coremod.FCClassTransformer;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class TileUnpackagerTransformer extends FCClassTransformer.ClassMapper {
+
+ public static final TileUnpackagerTransformer INSTANCE = new TileUnpackagerTransformer();
+
+ private TileUnpackagerTransformer() {
+ // NO-OP
+ }
+
+ @Override
+ protected ClassVisitor getClassMapper(ClassVisitor downstream) {
+ return new TransformTileUnpackager(Opcodes.ASM5, downstream);
+ }
+
+ private static class TransformTileUnpackager extends ClassVisitor {
+
+ TransformTileUnpackager(int api, ClassVisitor cv) {
+ super(api, cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ if (name.equals("emptyTrackers") && desc.equals("()V")) {
+ return new TransformEmptyTrackers(api, super.visitMethod(access, name, desc, signature, exceptions));
+ }
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ }
+
+ private static class TransformEmptyTrackers extends MethodVisitor {
+
+ private boolean gettingItemHandlerCap = false;
+
+ TransformEmptyTrackers(int api, MethodVisitor mv) {
+ super(api, mv);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ super.visitFieldInsn(opcode, owner, name, desc);
+ if (opcode == Opcodes.GETSTATIC && desc.equals("Lnet/minecraftforge/common/capabilities/Capability;")) {
+ gettingItemHandlerCap = name.equals("ITEM_HANDLER_CAPABILITY");
+ }
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ if (opcode == Opcodes.INVOKEVIRTUAL && gettingItemHandlerCap) {
+ switch (name) {
+ case "hasCapability":
+ if (desc.equals("(Lnet/minecraftforge/common/capabilities/Capability;Lnet/minecraft/util/EnumFacing;)Z")) {
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/glodblock/github/coremod/CoreModHooks", "checkForItemHandler",
+ "(Lnet/minecraftforge/common/capabilities/ICapabilityProvider;Lnet/minecraftforge/common/capabilities/Capability;" +
+ "Lnet/minecraft/util/EnumFacing;)Z", false);
+ gettingItemHandlerCap = false;
+ }
+ break;
+ case "getCapability":
+ if (desc.equals("(Lnet/minecraftforge/common/capabilities/Capability;Lnet/minecraft/util/EnumFacing;)Ljava/lang/Object;")) {
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/glodblock/github/coremod/CoreModHooks", "wrapItemHandler",
+ "(Lnet/minecraftforge/common/capabilities/ICapabilityProvider;Lnet/minecraftforge/common/capabilities/Capability;" +
+ "Lnet/minecraft/util/EnumFacing;)Lnet/minecraftforge/items/IItemHandler;", false);
+ gettingItemHandlerCap = false;
+ }
+ break;
+ default:
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ break;
+ }
+ } else {
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/handler/AeItemStackHandler.java b/src/main/java/com/glodblock/github/handler/AeItemStackHandler.java
new file mode 100644
index 000000000..106372dc2
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/AeItemStackHandler.java
@@ -0,0 +1,46 @@
+package com.glodblock.github.handler;
+
+import appeng.api.storage.data.IAEItemStack;
+import com.glodblock.github.interfaces.AeStackInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.items.IItemHandler;
+
+public class AeItemStackHandler implements IItemHandler {
+
+ private final AeStackInventory inv;
+
+ public AeItemStackHandler(AeStackInventory inv) {
+ this.inv = inv;
+ }
+
+ public AeStackInventory getAeInventory() {
+ return inv;
+ }
+
+ @Override
+ public int getSlots() {
+ return inv.getSlotCount();
+ }
+
+ @Override
+ public ItemStack getStackInSlot(int slot) {
+ IAEItemStack stack = inv.getStack(slot);
+ return stack != null ? stack.createItemStack() : ItemStack.EMPTY;
+ }
+
+ @Override
+ public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) {
+ return ItemStack.EMPTY;
+ }
+
+ @Override
+ public ItemStack extractItem(int slot, int amount, boolean simulate) {
+ return ItemStack.EMPTY;
+ }
+
+ @Override
+ public int getSlotLimit(int slot) {
+ return 64;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/handler/ButtonMouseHandler.java b/src/main/java/com/glodblock/github/handler/ButtonMouseHandler.java
new file mode 100644
index 000000000..3050bfe6f
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/ButtonMouseHandler.java
@@ -0,0 +1,48 @@
+package com.glodblock.github.handler;
+
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.interfaces.TankDumpable;
+import com.glodblock.github.network.CPacketDumpTank;
+import com.glodblock.github.util.MouseRegionManager;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.client.resources.I18n;
+
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+
+public class ButtonMouseHandler implements MouseRegionManager.Handler {
+
+ @Nullable
+ private final String tooltipKey;
+ private final Runnable callback;
+
+ public ButtonMouseHandler(@Nullable String tooltipKey, Runnable callback) {
+ this.tooltipKey = tooltipKey;
+ this.callback = callback;
+ }
+
+ @Nullable
+ @Override
+ public List getTooltip() {
+ return tooltipKey != null ? Collections.singletonList(I18n.format(tooltipKey)) : null;
+ }
+
+ @Override
+ public boolean onClick(int button) {
+ if (button == 0) {
+ callback.run();
+ return true;
+ }
+ return false;
+ }
+
+ public static ButtonMouseHandler dumpTank(TankDumpable host, int index) {
+ return new ButtonMouseHandler(NameConst.TT_DUMP_TANK, () -> {
+ if (host.canDumpTank(index)) {
+ FluidCraft.proxy.netHandler.sendToServer(new CPacketDumpTank(index));
+ }
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/handler/ClientRegistryHandler.java b/src/main/java/com/glodblock/github/handler/ClientRegistryHandler.java
new file mode 100644
index 000000000..b325d9b4f
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/ClientRegistryHandler.java
@@ -0,0 +1,41 @@
+package com.glodblock.github.handler;
+
+import appeng.api.AEApi;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.model.DenseEncodedPatternModel;
+import com.glodblock.github.client.model.FluidPacketModel;
+import com.glodblock.github.common.part.PartDualInterface;
+import com.glodblock.github.common.part.PartFluidPatternTerminal;
+import com.glodblock.github.interfaces.HasCustomModel;
+import net.minecraft.block.Block;
+import net.minecraft.client.renderer.block.model.ModelResourceLocation;
+import net.minecraft.item.Item;
+import net.minecraftforge.client.event.ModelRegistryEvent;
+import net.minecraftforge.client.model.ModelLoader;
+import net.minecraftforge.client.model.ModelLoaderRegistry;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import org.apache.commons.lang3.tuple.Pair;
+
+public class ClientRegistryHandler extends RegistryHandler {
+
+ @SubscribeEvent
+ public void onRegisterModels(ModelRegistryEvent event) {
+ ModelLoaderRegistry.registerLoader(new DenseEncodedPatternModel.Loader());
+ ModelLoaderRegistry.registerLoader(new FluidPacketModel.Loader());
+ for (Pair entry : blocks) {
+ registerModel(entry.getLeft(), Item.getItemFromBlock(entry.getRight()));
+ }
+ for (Pair entry : items) {
+ registerModel(entry.getLeft(), entry.getRight());
+ }
+ AEApi.instance().registries().partModels().registerModels(PartDualInterface.MODELS);
+ AEApi.instance().registries().partModels().registerModels(PartFluidPatternTerminal.MODELS);
+ }
+
+ private static void registerModel(String key, Item item) {
+ ModelLoader.setCustomModelResourceLocation(item, 0, new ModelResourceLocation(
+ item instanceof HasCustomModel ? ((HasCustomModel)item).getCustomModelPath() : FluidCraft.resource(key),
+ "inventory"));
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/handler/FluidConvertingItemHandler.java b/src/main/java/com/glodblock/github/handler/FluidConvertingItemHandler.java
new file mode 100644
index 000000000..2317eb845
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/FluidConvertingItemHandler.java
@@ -0,0 +1,147 @@
+package com.glodblock.github.handler;
+
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumFacing;
+import net.minecraftforge.common.capabilities.ICapabilityProvider;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
+import net.minecraftforge.fluids.capability.IFluidHandler;
+import net.minecraftforge.fluids.capability.IFluidTankProperties;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+public class FluidConvertingItemHandler implements IItemHandler {
+
+ public static FluidConvertingItemHandler wrap(ICapabilityProvider capProvider, EnumFacing face) {
+ // sometimes i wish i had the monadic version from 1.15
+ return new FluidConvertingItemHandler(
+ capProvider.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face)
+ ? Objects.requireNonNull(capProvider.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face))
+ : null,
+ capProvider.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face)
+ ? Objects.requireNonNull(capProvider.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face))
+ : null);
+ }
+
+ @Nullable
+ private final IItemHandler invItems;
+ @Nullable
+ private final IFluidHandler invFluids;
+
+ private FluidConvertingItemHandler(@Nullable IItemHandler invItems, @Nullable IFluidHandler invFluids) {
+ this.invItems = invItems;
+ this.invFluids = invFluids;
+ }
+
+ @Override
+ public int getSlots() {
+ int slots = 0;
+ if (invItems != null) {
+ slots += invItems.getSlots();
+ }
+ if (invFluids != null) {
+ slots += invFluids.getTankProperties().length;
+ }
+ return slots;
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack getStackInSlot(int slot) {
+ return slotOp(slot, IItemHandler::getStackInSlot,
+ (fh, i) -> ItemFluidDrop.newStack(fh.getTankProperties()[i].getContents()));
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) {
+ return slotOp(slot,
+ (ih, i) -> (stack.getItem() instanceof ItemFluidDrop || stack.getItem() instanceof ItemFluidPacket)
+ ? stack : ih.insertItem(i, stack, simulate),
+ (fh, i) -> {
+ if (stack.getItem() instanceof ItemFluidDrop) {
+ FluidStack toInsert = ItemFluidDrop.getFluidStack(stack);
+ if (toInsert != null && toInsert.amount > 0) {
+ FluidStack contained = fh.getTankProperties()[i].getContents();
+ if (contained == null || contained.amount == 0 || contained.isFluidEqual(toInsert)) {
+ toInsert.amount -= fh.fill(toInsert, !simulate);
+ return ItemFluidDrop.newStack(toInsert);
+ }
+ }
+ } else if (stack.getItem() instanceof ItemFluidPacket) {
+ FluidStack toInsert = ItemFluidPacket.getFluidStack(stack);
+ if (toInsert != null && toInsert.amount > 0) {
+ FluidStack contained = fh.getTankProperties()[i].getContents();
+ if (contained == null || contained.amount == 0 || contained.isFluidEqual(toInsert)) {
+ int insertable = fh.fill(toInsert, false); // only insert if the entire packet fits
+ if (insertable >= toInsert.amount) {
+ if (!simulate) {
+ fh.fill(toInsert, true);
+ }
+ return ItemStack.EMPTY;
+ }
+ }
+ }
+ }
+ return stack;
+ });
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack extractItem(int slot, int amount, boolean simulate) {
+ return slotOp(slot, (ih, i) -> ih.extractItem(i, slot, simulate), (fh, i) -> {
+ FluidStack contained = fh.getTankProperties()[i].getContents();
+ if (contained != null && contained.amount > 0) {
+ return ItemFluidDrop.newStack(fh.drain(contained, !simulate));
+ }
+ return ItemStack.EMPTY;
+ });
+ }
+
+ @Override
+ public int getSlotLimit(int slot) {
+ return slotOp(slot, IItemHandler::getSlotLimit, (fh, i) -> fh.getTankProperties()[i].getCapacity());
+ }
+
+ @Override
+ public boolean isItemValid(int slot, @Nonnull ItemStack stack) {
+ return slotOp(slot, (ih, i) -> ih.isItemValid(i, stack),
+ (fh, i) -> stack.getItem() instanceof ItemFluidDrop || stack.getItem() instanceof ItemFluidPacket);
+ }
+
+ private T slotOp(int slot, Op itemConsumer, Op fluidConsumer) {
+ if (slot >= 0) {
+ int fluidSlot = slot;
+ if (invItems != null) {
+ if (slot < invItems.getSlots()) {
+ return itemConsumer.apply(invItems, slot);
+ } else {
+ fluidSlot -= invItems.getSlots();
+ }
+ }
+ if (invFluids != null) {
+ IFluidTankProperties[] tanks = invFluids.getTankProperties();
+ if (fluidSlot < tanks.length) {
+ return fluidConsumer.apply(invFluids, fluidSlot);
+ }
+ }
+ }
+ throw new IndexOutOfBoundsException(String.format("Slot index %d out of bounds! |items| = %d, |fluids| = %d", slot,
+ invItems != null ? invItems.getSlots() : 0, invFluids != null ? invFluids.getTankProperties().length : 0));
+ }
+
+ @FunctionalInterface
+ private interface Op {
+
+ T apply(C collection, int index);
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/handler/RegistryHandler.java b/src/main/java/com/glodblock/github/handler/RegistryHandler.java
new file mode 100644
index 000000000..fde38289b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/RegistryHandler.java
@@ -0,0 +1,74 @@
+package com.glodblock.github.handler;
+
+import appeng.block.AEBaseItemBlock;
+import appeng.block.AEBaseTileBlock;
+import appeng.core.features.ActivityState;
+import appeng.core.features.BlockStackSrc;
+import appeng.tile.AEBaseTile;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.loader.FCItems;
+import net.minecraft.block.Block;
+import net.minecraft.item.Item;
+import net.minecraftforge.event.RegistryEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.registry.ForgeRegistries;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RegistryHandler {
+
+ protected final List> blocks = new ArrayList<>();
+ protected final List> items = new ArrayList<>();
+
+ public void block(String name, Block block) {
+ blocks.add(Pair.of(name, block));
+ }
+
+ public void item(String name, Item item) {
+ items.add(Pair.of(name, item));
+ }
+
+ @SubscribeEvent
+ public void onRegisterBlocks(RegistryEvent.Register event) {
+ for (Pair entry : blocks) {
+ String key = entry.getLeft();
+ Block block = entry.getRight();
+ block.setRegistryName(key);
+ block.setTranslationKey(FluidCraft.MODID + ":" + key);
+ block.setCreativeTab(FCItems.TAB_AE2FC);
+ event.getRegistry().register(block);
+ }
+ }
+
+ @SubscribeEvent
+ public void onRegisterItems(RegistryEvent.Register- event) {
+ // TODO some way to handle blocks with custom ItemBlock
+ for (Pair entry : blocks) {
+ event.getRegistry().register(initItem(entry.getLeft(), new AEBaseItemBlock(entry.getRight())));
+ }
+ for (Pair entry : items) {
+ event.getRegistry().register(initItem(entry.getLeft(), entry.getRight()));
+ }
+ }
+
+ private static Item initItem(String key, Item item) {
+ item.setRegistryName(key);
+ item.setTranslationKey(FluidCraft.MODID + ":" + key);
+ item.setCreativeTab(FCItems.TAB_AE2FC);
+ return item;
+ }
+
+ public void onInit() {
+ for (Pair entry : blocks) {
+ // respects registry overrides, i guess
+ Block block = ForgeRegistries.BLOCKS.getValue(FluidCraft.resource(entry.getKey()));
+ if (block instanceof AEBaseTileBlock) {
+ AEBaseTile.registerTileItem(((AEBaseTileBlock)block).getTileEntityClass(),
+ new BlockStackSrc(block, 0, ActivityState.Enabled));
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/handler/TankMouseHandler.java b/src/main/java/com/glodblock/github/handler/TankMouseHandler.java
new file mode 100644
index 000000000..b95d120ac
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/TankMouseHandler.java
@@ -0,0 +1,34 @@
+package com.glodblock.github.handler;
+
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.fluids.util.IAEFluidTank;
+import com.glodblock.github.util.MouseRegionManager;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.util.text.TextFormatting;
+
+import javax.annotation.Nullable;
+import java.util.Arrays;
+import java.util.List;
+
+public class TankMouseHandler implements MouseRegionManager.Handler {
+
+ private final IAEFluidTank tank;
+ private final int index;
+
+ public TankMouseHandler(IAEFluidTank tank, int index) {
+ this.tank = tank;
+ this.index = index;
+ }
+
+ @Nullable
+ @Override
+ public List getTooltip() {
+ IAEFluidStack fluid = tank.getFluidInSlot(index);
+ return Arrays.asList(
+ fluid != null ? fluid.getFluidStack().getLocalizedName() : I18n.format(NameConst.TT_EMPTY),
+ TextFormatting.GRAY + String.format("%,d / %,d mB",
+ fluid != null ? fluid.getStackSize() : 0L, tank.getTankProperties()[index].getCapacity()));
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/jei/ExtraExtractors.java b/src/main/java/com/glodblock/github/integration/jei/ExtraExtractors.java
new file mode 100644
index 000000000..cf455ecce
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/jei/ExtraExtractors.java
@@ -0,0 +1,23 @@
+package com.glodblock.github.integration.jei;
+
+import com.glodblock.github.integration.jei.interfaces.IngredientExtractor;
+import mezz.jei.api.gui.IRecipeLayout;
+import net.minecraftforge.fluids.FluidStack;
+
+import javax.annotation.Nullable;
+import java.util.stream.Stream;
+
+public class ExtraExtractors {
+
+ @Nullable
+ private final IngredientExtractor extModMach;
+
+ public ExtraExtractors(@Nullable IngredientExtractor extModMach) {
+ this.extModMach = extModMach;
+ }
+
+ public Stream> extractFluids(IRecipeLayout recipeLayout) {
+ return extModMach != null ? extModMach.extract(recipeLayout) : Stream.empty();
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/jei/FCJeiPlugin.java b/src/main/java/com/glodblock/github/integration/jei/FCJeiPlugin.java
new file mode 100644
index 000000000..a2c83ec14
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/jei/FCJeiPlugin.java
@@ -0,0 +1,35 @@
+package com.glodblock.github.integration.jei;
+
+import com.glodblock.github.integration.jei.interfaces.IngredientExtractor;
+import mezz.jei.api.IModPlugin;
+import mezz.jei.api.IModRegistry;
+import mezz.jei.api.JEIPlugin;
+import mezz.jei.config.Constants;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fml.common.Loader;
+
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+@JEIPlugin
+public class FCJeiPlugin implements IModPlugin {
+
+ @Nullable
+ private static ExtraExtractors ext = null;
+
+ public static ExtraExtractors getExtraExtractors() {
+ return Objects.requireNonNull(ext);
+ }
+
+ @Override
+ public void register(IModRegistry registry) {
+ IngredientExtractor extModMach = Loader.isModLoaded("modularmachinery")
+ ? new ModMachHybridFluidStackExtractor(registry) : null;
+ ext = new ExtraExtractors(extModMach);
+ registry.getRecipeTransferRegistry().addRecipeTransferHandler(
+ new FluidPatternEncoderRecipeTransferHandler(ext), Constants.UNIVERSAL_RECIPE_TRANSFER_UID);
+ registry.getRecipeTransferRegistry().addRecipeTransferHandler(
+ new FluidPatternTerminalRecipeTransferHandler(ext), Constants.UNIVERSAL_RECIPE_TRANSFER_UID);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/integration/jei/FluidPatternEncoderRecipeTransferHandler.java b/src/main/java/com/glodblock/github/integration/jei/FluidPatternEncoderRecipeTransferHandler.java
new file mode 100644
index 000000000..e45f082d9
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/jei/FluidPatternEncoderRecipeTransferHandler.java
@@ -0,0 +1,105 @@
+package com.glodblock.github.integration.jei;
+
+import appeng.api.storage.data.IAEItemStack;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.container.ContainerFluidPatternEncoder;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.common.tile.TileFluidPatternEncoder;
+import com.glodblock.github.network.CPacketLoadPattern;
+import com.glodblock.github.util.NameConst;
+import mezz.jei.api.gui.IGuiIngredient;
+import mezz.jei.api.gui.IRecipeLayout;
+import mezz.jei.api.recipe.VanillaRecipeCategoryUid;
+import mezz.jei.api.recipe.transfer.IRecipeTransferError;
+import mezz.jei.api.recipe.transfer.IRecipeTransferHandler;
+import mezz.jei.transfer.RecipeTransferErrorTooltip;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Iterator;
+
+public class FluidPatternEncoderRecipeTransferHandler implements IRecipeTransferHandler {
+
+ private final ExtraExtractors ext;
+
+ public FluidPatternEncoderRecipeTransferHandler(ExtraExtractors ext) {
+ this.ext = ext;
+ }
+
+ @Override
+ @Nonnull
+ public Class getContainerClass() {
+ return ContainerFluidPatternEncoder.class;
+ }
+
+ @Nullable
+ @Override
+ public IRecipeTransferError transferRecipe(@Nonnull ContainerFluidPatternEncoder container, IRecipeLayout recipeLayout,
+ @Nonnull EntityPlayer player, boolean maxTransfer, boolean doTransfer) {
+ if (recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) {
+ return new RecipeTransferErrorTooltip(I18n.format(NameConst.TT_PROCESSING_RECIPE_ONLY));
+ }
+ if (doTransfer) {
+ TileFluidPatternEncoder tile = container.getTile();
+ IAEItemStack[] crafting = new IAEItemStack[tile.getCraftingSlots().getSlotCount()];
+ IAEItemStack[] output = new IAEItemStack[tile.getOutputSlots().getSlotCount()];
+ transferRecipeSlots(recipeLayout, crafting, output, false, ext);
+ FluidCraft.proxy.netHandler.sendToServer(new CPacketLoadPattern(crafting, output));
+ }
+ return null;
+ }
+
+ public static void transferRecipeSlots(IRecipeLayout recipeLayout, IAEItemStack[] crafting, IAEItemStack[] output,
+ boolean retainEmptyInputs, ExtraExtractors ext) {
+ int ndxCrafting = 0, ndxOutput = 0;
+ for (IGuiIngredient ing : recipeLayout.getItemStacks().getGuiIngredients().values()) {
+ if (ing.isInput()) {
+ if (ndxCrafting < crafting.length) {
+ ItemStack stack = ing.getDisplayedIngredient();
+ if (stack != null) {
+ crafting[ndxCrafting++] = AEItemStack.fromItemStack(stack);
+ } else if (retainEmptyInputs) {
+ crafting[ndxCrafting++] = null;
+ }
+ }
+ } else {
+ if (ndxOutput < output.length) {
+ ItemStack stack = ing.getDisplayedIngredient();
+ if (stack != null) {
+ output[ndxOutput++] = AEItemStack.fromItemStack(stack);
+ }
+ }
+ }
+ }
+ for (IGuiIngredient ing : recipeLayout.getFluidStacks().getGuiIngredients().values()) {
+ if (ing.isInput()) {
+ if (ndxCrafting < crafting.length) {
+ crafting[ndxCrafting++] = ItemFluidPacket.newAeStack(ing.getDisplayedIngredient());
+ }
+ } else {
+ if (ndxOutput < output.length) {
+ output[ndxOutput++] = ItemFluidPacket.newAeStack(ing.getDisplayedIngredient());
+ }
+ }
+ }
+ Iterator> iter = ext.extractFluids(recipeLayout).iterator();
+ while (iter.hasNext()) {
+ WrappedIngredient ing = iter.next();
+ if (ing.isInput()) {
+ if (ndxCrafting < crafting.length) {
+ crafting[ndxCrafting++] = ItemFluidPacket.newAeStack(ing.getIngredient());
+ }
+ } else {
+ if (ndxOutput < output.length) {
+ output[ndxOutput++] = ItemFluidPacket.newAeStack(ing.getIngredient());
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/jei/FluidPatternTerminalRecipeTransferHandler.java b/src/main/java/com/glodblock/github/integration/jei/FluidPatternTerminalRecipeTransferHandler.java
new file mode 100644
index 000000000..5b41dec06
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/jei/FluidPatternTerminalRecipeTransferHandler.java
@@ -0,0 +1,55 @@
+package com.glodblock.github.integration.jei;
+
+import appeng.api.storage.data.IAEItemStack;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.container.ContainerFluidPatternTerminal;
+import com.glodblock.github.common.part.PartFluidPatternTerminal;
+import com.glodblock.github.network.CPacketLoadPattern;
+import com.glodblock.github.util.NameConst;
+import mezz.jei.api.gui.IRecipeLayout;
+import mezz.jei.api.recipe.VanillaRecipeCategoryUid;
+import mezz.jei.api.recipe.transfer.IRecipeTransferError;
+import mezz.jei.api.recipe.transfer.IRecipeTransferHandler;
+import mezz.jei.transfer.RecipeTransferErrorTooltip;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.EntityPlayer;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class FluidPatternTerminalRecipeTransferHandler implements IRecipeTransferHandler {
+
+ private final ExtraExtractors ext;
+
+ FluidPatternTerminalRecipeTransferHandler(ExtraExtractors ext) {
+ this.ext = ext;
+ }
+
+ @Override
+ @Nonnull
+ public Class getContainerClass() {
+ return ContainerFluidPatternTerminal.class;
+ }
+
+ @Nullable
+ @Override
+ public IRecipeTransferError transferRecipe(@Nonnull ContainerFluidPatternTerminal container, @Nonnull IRecipeLayout recipeLayout,
+ @Nonnull EntityPlayer player, boolean maxTransfer, boolean doTransfer) {
+ if (container.craftingMode) {
+ if (!recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) {
+ return new RecipeTransferErrorTooltip(I18n.format(NameConst.TT_CRAFTING_RECIPE_ONLY));
+ }
+ } else if (recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) {
+ return new RecipeTransferErrorTooltip(I18n.format(NameConst.TT_PROCESSING_RECIPE_ONLY));
+ }
+ if (doTransfer && container.getPatternTerminal() instanceof PartFluidPatternTerminal) {
+ PartFluidPatternTerminal tile = (PartFluidPatternTerminal)container.getPatternTerminal();
+ IAEItemStack[] crafting = new IAEItemStack[tile.getInventoryByName("crafting").getSlots()];
+ IAEItemStack[] output = new IAEItemStack[tile.getInventoryByName("output").getSlots()];
+ FluidPatternEncoderRecipeTransferHandler.transferRecipeSlots(recipeLayout, crafting, output, container.craftingMode, ext);
+ FluidCraft.proxy.netHandler.sendToServer(new CPacketLoadPattern(crafting, output));
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/jei/ModMachHybridFluidStackExtractor.java b/src/main/java/com/glodblock/github/integration/jei/ModMachHybridFluidStackExtractor.java
new file mode 100644
index 000000000..ffa75f438
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/jei/ModMachHybridFluidStackExtractor.java
@@ -0,0 +1,29 @@
+package com.glodblock.github.integration.jei;
+
+import com.glodblock.github.integration.jei.interfaces.IngredientExtractor;
+import hellfirepvp.modularmachinery.common.integration.ingredient.HybridFluid;
+import mezz.jei.api.IModRegistry;
+import mezz.jei.api.gui.IRecipeLayout;
+import mezz.jei.api.recipe.IIngredientType;
+import net.minecraftforge.fluids.FluidStack;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+public class ModMachHybridFluidStackExtractor implements IngredientExtractor {
+
+ private final IIngredientType ingTypeHybridFluid;
+
+ ModMachHybridFluidStackExtractor(IModRegistry registry) {
+ ingTypeHybridFluid = Objects.requireNonNull(registry.getIngredientRegistry().getIngredientType(HybridFluid.class));
+ }
+
+ public Stream> extract(IRecipeLayout recipeLayout) {
+ return recipeLayout.getIngredientsGroup(ingTypeHybridFluid).getGuiIngredients().values().stream()
+ .map(ing -> {
+ HybridFluid hf = ing.getDisplayedIngredient();
+ return new WrappedIngredient<>(hf != null ? hf.asFluidStack() : null, ing.isInput());
+ });
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/jei/WrappedIngredient.java b/src/main/java/com/glodblock/github/integration/jei/WrappedIngredient.java
new file mode 100644
index 000000000..4d158e4ac
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/jei/WrappedIngredient.java
@@ -0,0 +1,25 @@
+package com.glodblock.github.integration.jei;
+
+import javax.annotation.Nullable;
+
+public class WrappedIngredient {
+
+ @Nullable
+ private final T ingredient;
+ private final boolean isInput;
+
+ public WrappedIngredient(@Nullable T ingredient, boolean isInput) {
+ this.ingredient = ingredient;
+ this.isInput = isInput;
+ }
+
+ @Nullable
+ public T getIngredient() {
+ return ingredient;
+ }
+
+ public boolean isInput() {
+ return isInput;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/jei/interfaces/IngredientExtractor.java b/src/main/java/com/glodblock/github/integration/jei/interfaces/IngredientExtractor.java
new file mode 100644
index 000000000..75347312e
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/jei/interfaces/IngredientExtractor.java
@@ -0,0 +1,12 @@
+package com.glodblock.github.integration.jei.interfaces;
+
+import com.glodblock.github.integration.jei.WrappedIngredient;
+import mezz.jei.api.gui.IRecipeLayout;
+
+import java.util.stream.Stream;
+
+public interface IngredientExtractor {
+
+ Stream> extract(IRecipeLayout recipeLayout);
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/pauto/PackagedFluidCrafting.java b/src/main/java/com/glodblock/github/integration/pauto/PackagedFluidCrafting.java
new file mode 100644
index 000000000..617c6da7c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/pauto/PackagedFluidCrafting.java
@@ -0,0 +1,19 @@
+package com.glodblock.github.integration.pauto;
+
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
+import thelm.packagedauto.api.RecipeTypeRegistry;
+
+public class PackagedFluidCrafting {
+
+ public static void init() {
+ RecipeTypeRegistry.registerRecipeType(RecipeTypeFluidProcessing.INSTANCE);
+ }
+
+ @SideOnly(Side.CLIENT)
+ public static void initClient() {
+ MinecraftForge.EVENT_BUS.register(new RecipeEncoderFluidTooltipHandler());
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/pauto/RecipeEncoderFluidTooltipHandler.java b/src/main/java/com/glodblock/github/integration/pauto/RecipeEncoderFluidTooltipHandler.java
new file mode 100644
index 000000000..d00e95199
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/pauto/RecipeEncoderFluidTooltipHandler.java
@@ -0,0 +1,34 @@
+package com.glodblock.github.integration.pauto;
+
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.loader.FCItems;
+import net.minecraft.client.Minecraft;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.text.TextFormatting;
+import net.minecraftforge.event.entity.player.ItemTooltipEvent;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import thelm.packagedauto.client.gui.GuiEncoder;
+
+import java.util.List;
+
+public class RecipeEncoderFluidTooltipHandler {
+
+ @SubscribeEvent(priority = EventPriority.HIGHEST)
+ public void onItemTooltip(ItemTooltipEvent event) {
+ if (Minecraft.getMinecraft().currentScreen instanceof GuiEncoder) {
+ ItemStack stack = event.getItemStack();
+ if (stack.getItem() == FCItems.FLUID_PACKET) {
+ FluidStack fluid = ItemFluidPacket.getFluidStack(stack);
+ if (fluid != null) {
+ List tooltip = event.getToolTip();
+ tooltip.clear();
+ tooltip.add(fluid.getLocalizedName());
+ tooltip.add(String.format(TextFormatting.GRAY + "%,d mB", fluid.amount));
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/pauto/RecipeInfoFluidProcessing.java b/src/main/java/com/glodblock/github/integration/pauto/RecipeInfoFluidProcessing.java
new file mode 100644
index 000000000..a23e79efa
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/pauto/RecipeInfoFluidProcessing.java
@@ -0,0 +1,13 @@
+package com.glodblock.github.integration.pauto;
+
+import thelm.packagedauto.api.IRecipeType;
+import thelm.packagedauto.recipe.RecipeInfoProcessing;
+
+public class RecipeInfoFluidProcessing extends RecipeInfoProcessing {
+
+ @Override
+ public IRecipeType getRecipeType() {
+ return RecipeTypeFluidProcessing.INSTANCE;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/pauto/RecipeTypeFluidProcessing.java b/src/main/java/com/glodblock/github/integration/pauto/RecipeTypeFluidProcessing.java
new file mode 100644
index 000000000..0ad3e602b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/pauto/RecipeTypeFluidProcessing.java
@@ -0,0 +1,150 @@
+package com.glodblock.github.integration.pauto;
+
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.integration.jei.FCJeiPlugin;
+import com.glodblock.github.integration.jei.WrappedIngredient;
+import com.glodblock.github.loader.FCBlocks;
+import it.unimi.dsi.fastutil.ints.*;
+import mezz.jei.api.gui.IGuiIngredient;
+import mezz.jei.api.gui.IRecipeLayout;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.translation.I18n;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fml.common.Optional;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
+import thelm.packagedauto.api.IRecipeInfo;
+import thelm.packagedauto.api.IRecipeType;
+import thelm.packagedauto.integration.jei.PackagedAutoJEIPlugin;
+
+import java.awt.*;
+import java.util.Iterator;
+import java.util.List;
+
+public class RecipeTypeFluidProcessing implements IRecipeType {
+
+ public static final RecipeTypeFluidProcessing INSTANCE = new RecipeTypeFluidProcessing();
+
+ private static final ResourceLocation NAME = FluidCraft.resource("fluid_processing");
+ private static final IntSet SLOTS;
+ private static final Color SLOT_COLOUR = new Color(0x8B8BAC);
+ private static final int NUM_SLOTS_CRAFT = 81, NUM_SLOTS_OUT = 9;
+
+ static {
+ IntSet slots = new IntOpenHashSet();
+ for (int i = 0; i < 90; i++) {
+ slots.add(i);
+ }
+ SLOTS = IntSets.unmodifiable(slots);
+ }
+
+ private RecipeTypeFluidProcessing() {
+ // NO-OP
+ }
+
+ @Override
+ public ResourceLocation getName() {
+ return NAME;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public String getLocalizedName() {
+ return I18n.translateToLocal("ae2fc.pauto.fluid_processing.name");
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public String getLocalizedNameShort() {
+ return I18n.translateToLocal("ae2fc.pauto.fluid_processing.name_short");
+ }
+
+ @Override
+ public IRecipeInfo getNewRecipeInfo() {
+ return new RecipeInfoFluidProcessing();
+ }
+
+ @Override
+ public IntSet getEnabledSlots() {
+ return SLOTS;
+ }
+
+ @Override
+ public boolean canSetOutput() {
+ return true;
+ }
+
+ @Override
+ public boolean hasMachine() {
+ return false;
+ }
+
+ @Override
+ public List getJEICategories() {
+ return PackagedAutoJEIPlugin.getAllRecipeCategories();
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ public Object getRepresentation() {
+ return new ItemStack(FCBlocks.FLUID_PATTERN_ENCODER);
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ public Color getSlotColor(int slot) {
+ return SLOT_COLOUR;
+ }
+
+ @Optional.Method(modid = "jei")
+ @Override
+ public Int2ObjectMap getRecipeTransferMap(IRecipeLayout recipeLayout, String category) {
+ Int2ObjectMap tfrs = new Int2ObjectOpenHashMap<>();
+ int ndxCrafting = 0, ndxOutput = 0;
+ for (IGuiIngredient ing : recipeLayout.getItemStacks().getGuiIngredients().values()) {
+ if (ing.isInput()) {
+ if (ndxCrafting < NUM_SLOTS_CRAFT) {
+ ItemStack stack = ing.getDisplayedIngredient();
+ if (stack != null) {
+ tfrs.put(ndxCrafting++, stack);
+ }
+ }
+ } else {
+ if (ndxOutput < NUM_SLOTS_OUT) {
+ ItemStack stack = ing.getDisplayedIngredient();
+ if (stack != null) {
+ tfrs.put(NUM_SLOTS_CRAFT + ndxOutput++, stack);
+ }
+ }
+ }
+ }
+ for (IGuiIngredient ing : recipeLayout.getFluidStacks().getGuiIngredients().values()) {
+ if (ing.isInput()) {
+ if (ndxCrafting < NUM_SLOTS_CRAFT) {
+ tfrs.put(ndxCrafting++, ItemFluidPacket.newStack(ing.getDisplayedIngredient()));
+ }
+ } else {
+ if (ndxOutput < NUM_SLOTS_OUT) {
+ tfrs.put(NUM_SLOTS_CRAFT + ndxOutput++, ItemFluidPacket.newStack(ing.getDisplayedIngredient()));
+ }
+ }
+ }
+ Iterator> iter = FCJeiPlugin.getExtraExtractors().extractFluids(recipeLayout).iterator();
+ while (iter.hasNext()) {
+ WrappedIngredient ing = iter.next();
+ if (ing.isInput()) {
+ if (ndxCrafting < NUM_SLOTS_CRAFT) {
+ tfrs.put(ndxCrafting++, ItemFluidPacket.newStack(ing.getIngredient()));
+ }
+ } else {
+ if (ndxOutput < NUM_SLOTS_OUT) {
+ tfrs.put(NUM_SLOTS_CRAFT + ndxOutput++, ItemFluidPacket.newStack(ing.getIngredient()));
+ }
+ }
+ }
+ return tfrs;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/interfaces/AeStackInventory.java b/src/main/java/com/glodblock/github/interfaces/AeStackInventory.java
new file mode 100644
index 000000000..bc8b01af8
--- /dev/null
+++ b/src/main/java/com/glodblock/github/interfaces/AeStackInventory.java
@@ -0,0 +1,19 @@
+package com.glodblock.github.interfaces;
+
+import appeng.api.storage.data.IAEStack;
+
+import javax.annotation.Nullable;
+import java.util.stream.Stream;
+
+public interface AeStackInventory > extends Iterable {
+
+ int getSlotCount();
+
+ @Nullable
+ T getStack(int slot);
+
+ void setStack(int slot, @Nullable T stack);
+
+ Stream stream();
+
+}
diff --git a/src/main/java/com/glodblock/github/interfaces/FCPriorityHost.java b/src/main/java/com/glodblock/github/interfaces/FCPriorityHost.java
new file mode 100644
index 000000000..af5f3aefe
--- /dev/null
+++ b/src/main/java/com/glodblock/github/interfaces/FCPriorityHost.java
@@ -0,0 +1,16 @@
+package com.glodblock.github.interfaces;
+
+import appeng.core.sync.GuiBridge;
+import appeng.helpers.IPriorityHost;
+import com.glodblock.github.inventory.GuiType;
+
+public interface FCPriorityHost extends IPriorityHost {
+
+ GuiType getGuiType();
+
+ @Override
+ default GuiBridge getGuiBridge() {
+ return GuiBridge.GUI_Handler;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/interfaces/HasCustomModel.java b/src/main/java/com/glodblock/github/interfaces/HasCustomModel.java
new file mode 100644
index 000000000..6fab7cb3c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/interfaces/HasCustomModel.java
@@ -0,0 +1,9 @@
+package com.glodblock.github.interfaces;
+
+import net.minecraft.util.ResourceLocation;
+
+public interface HasCustomModel {
+
+ ResourceLocation getCustomModelPath();
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/interfaces/PatternConsumer.java b/src/main/java/com/glodblock/github/interfaces/PatternConsumer.java
new file mode 100644
index 000000000..48ee01208
--- /dev/null
+++ b/src/main/java/com/glodblock/github/interfaces/PatternConsumer.java
@@ -0,0 +1,9 @@
+package com.glodblock.github.interfaces;
+
+import appeng.api.storage.data.IAEItemStack;
+
+public interface PatternConsumer {
+
+ void acceptPattern(IAEItemStack[] inputs, IAEItemStack[] outputs);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/interfaces/SlotFluid.java b/src/main/java/com/glodblock/github/interfaces/SlotFluid.java
new file mode 100644
index 000000000..506ad2b24
--- /dev/null
+++ b/src/main/java/com/glodblock/github/interfaces/SlotFluid.java
@@ -0,0 +1,14 @@
+package com.glodblock.github.interfaces;
+
+import appeng.api.storage.data.IAEItemStack;
+
+import javax.annotation.Nullable;
+
+public interface SlotFluid {
+
+ @Nullable
+ IAEItemStack getAeStack();
+
+ void setAeStack(@Nullable IAEItemStack stack, boolean sync);
+
+}
diff --git a/src/main/java/com/glodblock/github/interfaces/TankDumpable.java b/src/main/java/com/glodblock/github/interfaces/TankDumpable.java
new file mode 100644
index 000000000..97361d8d0
--- /dev/null
+++ b/src/main/java/com/glodblock/github/interfaces/TankDumpable.java
@@ -0,0 +1,9 @@
+package com.glodblock.github.interfaces;
+
+public interface TankDumpable {
+
+ boolean canDumpTank(int index);
+
+ void dumpTank(int index);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/inventory/AeStackInventoryImpl.java b/src/main/java/com/glodblock/github/inventory/AeStackInventoryImpl.java
new file mode 100644
index 000000000..59e669e2b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/inventory/AeStackInventoryImpl.java
@@ -0,0 +1,98 @@
+package com.glodblock.github.inventory;
+
+import appeng.api.storage.IStorageChannel;
+import appeng.api.storage.data.IAEStack;
+import appeng.util.inv.IAEAppEngInventory;
+import com.glodblock.github.interfaces.AeStackInventory;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraftforge.common.util.Constants;
+import org.apache.logging.log4j.core.util.ObjectArrayIterator;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.stream.Stream;
+
+public class AeStackInventoryImpl > implements AeStackInventory {
+
+ private final IStorageChannel channel;
+ private final T[] inv;
+ @Nullable
+ private final IAEAppEngInventory owner;
+
+ @SuppressWarnings("unchecked")
+ public AeStackInventoryImpl(IStorageChannel channel, int slotCount, @Nullable IAEAppEngInventory owner) {
+ this.channel = channel;
+ this.inv = (T[])new IAEStack[slotCount];
+ this.owner = owner;
+ }
+
+ public AeStackInventoryImpl(IStorageChannel channel, int slotCount) {
+ this(channel, slotCount, null);
+ }
+
+ @Override
+ public int getSlotCount() {
+ return inv.length;
+ }
+
+ @Override
+ @Nullable
+ public T getStack(int slot) {
+ return inv[slot];
+ }
+
+ @Override
+ public void setStack(int slot, @Nullable T stack) {
+ inv[slot] = stack;
+ if (owner != null) {
+ owner.saveChanges();
+ }
+ }
+
+ @Override
+ @Nonnull
+ public Iterator iterator() {
+ return new ObjectArrayIterator<>(inv);
+ }
+
+ @Override
+ public Stream stream() {
+ return Arrays.stream(inv);
+ }
+
+ public void writeToNbt(NBTTagCompound tag) {
+ NBTTagList stacksTag = new NBTTagList();
+ for (T stack : inv) {
+ if (stack == null) {
+ stacksTag.appendTag(new NBTTagCompound());
+ } else {
+ NBTTagCompound stackTag = new NBTTagCompound();
+ stack.writeToNBT(stackTag);
+ stacksTag.appendTag(stackTag);
+ }
+ }
+ tag.setTag("Contents", stacksTag);
+ }
+
+ public void writeToNbt(NBTTagCompound parentTag, String key) {
+ NBTTagCompound tag = new NBTTagCompound();
+ writeToNbt(tag);
+ parentTag.setTag(key, tag);
+ }
+
+ public void readFromNbt(NBTTagCompound tag) {
+ NBTTagList stacksTag = tag.getTagList("Contents", Constants.NBT.TAG_COMPOUND);
+ for (int i = 0; i < inv.length; i++) {
+ NBTTagCompound stackTag = stacksTag.getCompoundTagAt(i);
+ inv[i] = stackTag.isEmpty() ? null : channel.createFromNBT(stackTag);
+ }
+ }
+
+ public void readFromNbt(NBTTagCompound parentTag, String key) {
+ readFromNbt(parentTag.getCompoundTag(key));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java
new file mode 100644
index 000000000..f463f6479
--- /dev/null
+++ b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java
@@ -0,0 +1,166 @@
+package com.glodblock.github.inventory;
+
+import appeng.api.config.FuzzyMode;
+import appeng.util.InventoryAdaptor;
+import appeng.util.inv.AdaptorItemHandler;
+import appeng.util.inv.IInventoryDestination;
+import appeng.util.inv.ItemSlot;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.util.Ae2Reflect;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumFacing;
+import net.minecraftforge.common.capabilities.ICapabilityProvider;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
+import net.minecraftforge.fluids.capability.IFluidHandler;
+import net.minecraftforge.fluids.capability.IFluidTankProperties;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Objects;
+
+public class FluidConvertingInventoryAdaptor extends InventoryAdaptor {
+
+ public static FluidConvertingInventoryAdaptor wrap(ICapabilityProvider capProvider, EnumFacing face) {
+ // sometimes i wish i had the monadic version from 1.15
+ return new FluidConvertingInventoryAdaptor(
+ capProvider.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face)
+ ? Objects.requireNonNull(capProvider.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face))
+ : null,
+ capProvider.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face)
+ ? Objects.requireNonNull(capProvider.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face))
+ : null);
+ }
+
+ @Nullable
+ private final InventoryAdaptor invItems;
+ @Nullable
+ private final IFluidHandler invFluids;
+
+ public FluidConvertingInventoryAdaptor(@Nullable IItemHandler invItems, @Nullable IFluidHandler invFluids) {
+ this.invItems = invItems != null ? new AdaptorItemHandler(invItems) : null;
+ this.invFluids = invFluids;
+ }
+
+ @Override
+ public ItemStack addItems(ItemStack toBeAdded) {
+ if (toBeAdded.getItem() instanceof ItemFluidPacket) {
+ if (invFluids != null) {
+ FluidStack fluid = ItemFluidPacket.getFluidStack(toBeAdded);
+ if (fluid != null) {
+ int filled = invFluids.fill(fluid, true);
+ if (filled > 0) {
+ fluid.amount -= filled;
+ return ItemFluidPacket.newStack(fluid);
+ }
+ }
+ }
+ return toBeAdded;
+ }
+ return invItems != null ? invItems.addItems(toBeAdded) : toBeAdded;
+ }
+
+ @Override
+ public ItemStack simulateAdd(ItemStack toBeSimulated) {
+ if (toBeSimulated.getItem() instanceof ItemFluidPacket) {
+ if (invFluids != null) {
+ FluidStack fluid = ItemFluidPacket.getFluidStack(toBeSimulated);
+ if (fluid != null) {
+ int filled = invFluids.fill(fluid, false);
+ if (filled > 0) {
+ fluid.amount -= filled;
+ return ItemFluidPacket.newStack(fluid);
+ }
+ }
+ }
+ return toBeSimulated;
+ }
+ return invItems != null ? invItems.simulateAdd(toBeSimulated) : toBeSimulated;
+ }
+
+ @Override
+ public ItemStack removeItems(int amount, ItemStack filter, IInventoryDestination destination) {
+ return invItems != null ? invItems.removeItems(amount, filter, destination) : ItemStack.EMPTY;
+ }
+
+ @Override
+ public ItemStack simulateRemove(int amount, ItemStack filter, IInventoryDestination destination) {
+ return invItems != null ? invItems.simulateRemove(amount, filter, destination) : ItemStack.EMPTY;
+ }
+
+ @Override
+ public ItemStack removeSimilarItems(int amount, ItemStack filter, FuzzyMode fuzzyMode, IInventoryDestination destination) {
+ return invItems != null ? invItems.removeSimilarItems(amount, filter, fuzzyMode, destination) : ItemStack.EMPTY;
+ }
+
+ @Override
+ public ItemStack simulateSimilarRemove(int amount, ItemStack filter, FuzzyMode fuzzyMode, IInventoryDestination destination) {
+ return invItems != null ? invItems.simulateSimilarRemove(amount, filter, fuzzyMode, destination) : ItemStack.EMPTY;
+ }
+
+ @Override
+ public boolean containsItems() {
+ if (invFluids != null) {
+ for (IFluidTankProperties tank : invFluids.getTankProperties()) {
+ FluidStack fluid = tank.getContents();
+ if (fluid != null && fluid.amount > 0) {
+ return true;
+ }
+ }
+ }
+ return invItems != null && invItems.containsItems();
+ }
+
+ @Override
+ public boolean hasSlots() {
+ return (invFluids != null && invFluids.getTankProperties().length > 0)
+ || (invItems != null && invItems.hasSlots());
+ }
+
+ @Override
+ @Nonnull
+ public Iterator iterator() {
+ return new SlotIterator(
+ invFluids != null ? invFluids.getTankProperties() : new IFluidTankProperties[0],
+ invItems != null ? invItems.iterator() : Collections.emptyIterator());
+ }
+
+ private static class SlotIterator implements Iterator {
+
+ private final IFluidTankProperties[] tanks;
+ private final Iterator itemSlots;
+ private int nextSlotIndex = 0;
+
+ SlotIterator(IFluidTankProperties[] tanks, Iterator itemSlots) {
+ this.tanks = tanks;
+ this.itemSlots = itemSlots;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nextSlotIndex < tanks.length || itemSlots.hasNext();
+ }
+
+ @Override
+ public ItemSlot next() {
+ if (nextSlotIndex < tanks.length) {
+ FluidStack fluid = tanks[nextSlotIndex].getContents();
+ ItemSlot slot = new ItemSlot();
+ slot.setSlot(nextSlotIndex++);
+ slot.setItemStack(fluid != null ? ItemFluidPacket.newStack(fluid) : ItemStack.EMPTY);
+ Ae2Reflect.setItemSlotExtractable(slot, false);
+ return slot;
+ } else {
+ ItemSlot slot = itemSlots.next();
+ slot.setSlot(nextSlotIndex++);
+ return slot;
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryCrafting.java b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryCrafting.java
new file mode 100644
index 000000000..f3fdb7bbf
--- /dev/null
+++ b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryCrafting.java
@@ -0,0 +1,32 @@
+package com.glodblock.github.inventory;
+
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import net.minecraft.inventory.Container;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+
+public class FluidConvertingInventoryCrafting extends InventoryCrafting {
+
+ public FluidConvertingInventoryCrafting(Container container, int width, int height) {
+ super(container, width, height);
+ }
+
+ @Override
+ public void setInventorySlotContents(int index, ItemStack stack) {
+ if (stack.getItem() instanceof ItemFluidDrop) {
+ FluidStack fluid = ItemFluidDrop.getFluidStack(stack);
+ if (fluid != null) {
+ super.setInventorySlotContents(index, ItemFluidPacket.newStack(new FluidStack(fluid, stack.getCount())));
+ } else {
+ // wtf?
+ super.setInventorySlotContents(index, ItemFluidPacket.newStack(new FluidStack(FluidRegistry.WATER, 1000)));
+ }
+ } else {
+ super.setInventorySlotContents(index, stack);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/inventory/GuiType.java b/src/main/java/com/glodblock/github/inventory/GuiType.java
new file mode 100644
index 000000000..b6d824bc4
--- /dev/null
+++ b/src/main/java/com/glodblock/github/inventory/GuiType.java
@@ -0,0 +1,265 @@
+package com.glodblock.github.inventory;
+
+import appeng.api.parts.IPart;
+import appeng.api.parts.IPartHost;
+import appeng.api.storage.ITerminalHost;
+import appeng.api.util.AEPartLocation;
+import appeng.container.AEBaseContainer;
+import appeng.container.ContainerOpenContext;
+import appeng.container.implementations.ContainerCraftingStatus;
+import appeng.container.implementations.ContainerInterface;
+import appeng.container.implementations.ContainerPriority;
+import appeng.fluids.container.ContainerFluidInterface;
+import appeng.fluids.helper.IFluidInterfaceHost;
+import appeng.helpers.IInterfaceHost;
+import com.glodblock.github.client.*;
+import com.glodblock.github.client.container.*;
+import com.glodblock.github.common.part.PartFluidPatternTerminal;
+import com.glodblock.github.common.tile.TileBurette;
+import com.glodblock.github.common.tile.TileFluidPacketDecoder;
+import com.glodblock.github.common.tile.TileFluidPatternEncoder;
+import com.glodblock.github.common.tile.TileIngredientBuffer;
+import com.glodblock.github.interfaces.FCPriorityHost;
+import com.google.common.collect.ImmutableList;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
+
+import javax.annotation.Nullable;
+import java.util.List;
+
+public enum GuiType {
+
+ INGREDIENT_BUFFER(new TileGuiFactory(TileIngredientBuffer.class) {
+ @Override
+ protected Object createServerGui(EntityPlayer player, TileIngredientBuffer inv) {
+ return new ContainerIngredientBuffer(player.inventory, inv);
+ }
+
+ @Override
+ protected Object createClientGui(EntityPlayer player, TileIngredientBuffer inv) {
+ return new GuiIngredientBuffer(player.inventory, inv);
+ }
+ }),
+
+ FLUID_PATTERN_ENCODER(new TileGuiFactory(TileFluidPatternEncoder.class) {
+ @Override
+ protected Object createServerGui(EntityPlayer player, TileFluidPatternEncoder inv) {
+ return new ContainerFluidPatternEncoder(player.inventory, inv);
+ }
+
+ @Override
+ protected Object createClientGui(EntityPlayer player, TileFluidPatternEncoder inv) {
+ return new GuiFluidPatternEncoder(player.inventory, inv);
+ }
+ }),
+
+ FLUID_PACKET_DECODER(new TileGuiFactory(TileFluidPacketDecoder.class) {
+ @Override
+ protected Object createServerGui(EntityPlayer player, TileFluidPacketDecoder inv) {
+ return new ContainerFluidPacketDecoder(player.inventory, inv);
+ }
+
+ @Override
+ protected Object createClientGui(EntityPlayer player, TileFluidPacketDecoder inv) {
+ return new GuiFluidPacketDecoder(player.inventory, inv);
+ }
+ }),
+
+ PRECISION_BURETTE(new TileGuiFactory(TileBurette.class) {
+ @Override
+ protected Object createServerGui(EntityPlayer player, TileBurette inv) {
+ return new ContainerBurette(player.inventory, inv);
+ }
+
+ @Override
+ protected Object createClientGui(EntityPlayer player, TileBurette inv) {
+ return new GuiBurette(player.inventory, inv);
+ }
+ }),
+
+ DUAL_ITEM_INTERFACE(new PartOrTileGuiFactory(IInterfaceHost.class) {
+ @Override
+ protected Object createServerGui(EntityPlayer player, IInterfaceHost inv) {
+ return new ContainerInterface(player.inventory, inv);
+ }
+
+ @Override
+ protected Object createClientGui(EntityPlayer player, IInterfaceHost inv) {
+ return new GuiItemDualInterface(player.inventory, inv);
+ }
+ }),
+
+ DUAL_FLUID_INTERFACE(new PartOrTileGuiFactory(IFluidInterfaceHost.class) {
+ @Override
+ protected Object createServerGui(EntityPlayer player, IFluidInterfaceHost inv) {
+ return new ContainerFluidInterface(player.inventory, inv);
+ }
+
+ @Override
+ protected Object createClientGui(EntityPlayer player, IFluidInterfaceHost inv) {
+ return new GuiFluidDualInterface(player.inventory, inv);
+ }
+ }),
+
+ FLUID_PAT_TERM_CRAFTING_STATUS(new PartGuiFactory(PartFluidPatternTerminal.class) {
+ @Override
+ protected Object createServerGui(EntityPlayer player, PartFluidPatternTerminal inv) {
+ return new ContainerCraftingStatus(player.inventory, inv);
+ }
+
+ @Override
+ protected Object createClientGui(EntityPlayer player, PartFluidPatternTerminal inv) {
+ return new GuiFluidPatternTerminalCraftingStatus(player.inventory, inv);
+ }
+ }),
+
+ FLUID_PATTERN_TERMINAL(new PartGuiFactory(ITerminalHost.class) {
+ @Override
+ protected Object createServerGui(EntityPlayer player, ITerminalHost inv) {
+ return new ContainerFluidPatternTerminal(player.inventory, inv);
+ }
+
+ @Override
+ protected Object createClientGui(EntityPlayer player, ITerminalHost inv) {
+ return new GuiFluidPatternTerminal(player.inventory, inv);
+ }
+ }),
+
+ PRIORITY(new PartOrTileGuiFactory(FCPriorityHost.class) {
+ @Override
+ protected Object createServerGui(EntityPlayer player, FCPriorityHost inv) {
+ return new ContainerPriority(player.inventory, inv);
+ }
+
+ @Override
+ protected Object createClientGui(EntityPlayer player, FCPriorityHost inv) {
+ return new GuiFCPriority(player.inventory, inv);
+ }
+ });
+
+ public static final List VALUES = ImmutableList.copyOf(values());
+
+ @Nullable
+ public static GuiType getByOrdinal(int ordinal) {
+ return ordinal < 0 || ordinal >= VALUES.size() ? null : VALUES.get(ordinal);
+ }
+
+ final GuiFactory guiFactory;
+
+ GuiType(GuiFactory guiFactory) {
+ this.guiFactory = guiFactory;
+ }
+
+ public interface GuiFactory {
+
+ @Nullable
+ Object createServerGui(EntityPlayer player, World world, int x, int y, int z, EnumFacing face);
+
+ @SideOnly(Side.CLIENT)
+ @Nullable
+ Object createClientGui(EntityPlayer player, World world, int x, int y, int z, EnumFacing face);
+
+ }
+
+ private static abstract class TileGuiFactory implements GuiFactory {
+
+ protected final Class invClass;
+
+ TileGuiFactory(Class invClass) {
+ this.invClass = invClass;
+ }
+
+ @Nullable
+ protected T getInventory(TileEntity tile, EnumFacing face) {
+ return invClass.isInstance(tile) ? invClass.cast(tile) : null;
+ }
+
+ @Nullable
+ @Override
+ public Object createServerGui(EntityPlayer player, World world, int x, int y, int z, EnumFacing face) {
+ TileEntity tile = world.getTileEntity(new BlockPos(x, y, z));
+ if (tile == null) {
+ return null;
+ }
+ T inv = getInventory(tile, face);
+ if (inv == null) {
+ return null;
+ }
+ Object gui = createServerGui(player, inv);
+ if (gui instanceof AEBaseContainer) {
+ ContainerOpenContext ctx = new ContainerOpenContext(inv);
+ ctx.setWorld(world);
+ ctx.setX(x);
+ ctx.setY(y);
+ ctx.setZ(z);
+ ctx.setSide(AEPartLocation.fromFacing(face));
+ ((AEBaseContainer)gui).setOpenContext(ctx);
+ }
+ return gui;
+ }
+
+ @Nullable
+ protected abstract Object createServerGui(EntityPlayer player, T inv);
+
+ @Nullable
+ @Override
+ public Object createClientGui(EntityPlayer player, World world, int x, int y, int z, EnumFacing face) {
+ TileEntity tile = world.getTileEntity(new BlockPos(x, y, z));
+ if (tile == null) {
+ return null;
+ }
+ T inv = getInventory(tile, face);
+ return inv != null ? createClientGui(player, inv) : null;
+ }
+
+ @Nullable
+ protected abstract Object createClientGui(EntityPlayer player, T inv);
+
+ }
+
+ private static abstract class PartOrTileGuiFactory extends TileGuiFactory {
+
+ PartOrTileGuiFactory(Class invClass) {
+ super(invClass);
+ }
+
+ @Nullable
+ @Override
+ protected T getInventory(TileEntity tile, EnumFacing face) {
+ if (tile instanceof IPartHost) {
+ IPart part = ((IPartHost)tile).getPart(face);
+ if (invClass.isInstance(part)) {
+ return invClass.cast(part);
+ }
+ }
+ return super.getInventory(tile, face);
+ }
+
+ }
+
+ private static abstract class PartGuiFactory extends TileGuiFactory {
+
+ PartGuiFactory(Class invClass) {
+ super(invClass);
+ }
+
+ @Nullable
+ @Override
+ protected T getInventory(TileEntity tile, EnumFacing face) {
+ if (tile instanceof IPartHost) {
+ IPart part = ((IPartHost)tile).getPart(face);
+ if (invClass.isInstance(part)) {
+ return invClass.cast(part);
+ }
+ }
+ return null;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/inventory/InventoryHandler.java b/src/main/java/com/glodblock/github/inventory/InventoryHandler.java
new file mode 100644
index 000000000..4561fdb47
--- /dev/null
+++ b/src/main/java/com/glodblock/github/inventory/InventoryHandler.java
@@ -0,0 +1,51 @@
+package com.glodblock.github.inventory;
+
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.network.CPacketSwitchGuis;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraftforge.fml.common.network.IGuiHandler;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
+
+import javax.annotation.Nullable;
+
+public class InventoryHandler implements IGuiHandler {
+
+ public static void switchGui(GuiType guiType) {
+ FluidCraft.proxy.netHandler.sendToServer(new CPacketSwitchGuis(guiType));
+ }
+
+ public static void openGui(EntityPlayer player, World world, BlockPos pos, EnumFacing face, GuiType guiType) {
+ player.openGui(FluidCraft.INSTANCE,
+ (guiType.ordinal() << 3) | face.ordinal(), world, pos.getX(), pos.getY(), pos.getZ());
+ }
+
+ @Nullable
+ @Override
+ public Object getServerGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) {
+ int faceOrd = id & 0x7;
+ if (faceOrd > EnumFacing.VALUES.length) {
+ return null;
+ }
+ EnumFacing face = EnumFacing.VALUES[faceOrd];
+ GuiType type = GuiType.getByOrdinal(id >>> 3);
+ return type != null ? type.guiFactory.createServerGui(player, world, x, y, z, face) : null;
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Nullable
+ @Override
+ public Object getClientGuiElement(int id, EntityPlayer player, World world, int x, int y, int z) {
+ int faceOrd = id & 0x7;
+ if (faceOrd > EnumFacing.VALUES.length) {
+ return null;
+ }
+ EnumFacing face = EnumFacing.VALUES[faceOrd];
+ GuiType type = GuiType.getByOrdinal(id >>> 3);
+ return type != null ? type.guiFactory.createClientGui(player, world, x, y, z, face) : null;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/inventory/slot/SlotSingleItem.java b/src/main/java/com/glodblock/github/inventory/slot/SlotSingleItem.java
new file mode 100644
index 000000000..d0026120d
--- /dev/null
+++ b/src/main/java/com/glodblock/github/inventory/slot/SlotSingleItem.java
@@ -0,0 +1,139 @@
+package com.glodblock.github.inventory.slot;
+
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
+import net.minecraftforge.items.ItemHandlerHelper;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class SlotSingleItem extends Slot {
+
+ private final Slot delegate;
+
+ public SlotSingleItem(Slot delegate) {
+ super(delegate.inventory, delegate.getSlotIndex(), delegate.xPos, delegate.yPos);
+ this.delegate = delegate;
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack getStack() {
+ ItemStack stack = delegate.getStack();
+ return stack.isEmpty() ? ItemStack.EMPTY : ItemHandlerHelper.copyStackWithSize(stack, 1);
+ }
+
+ // delegated
+
+ @Override
+ public void onSlotChange(@Nonnull ItemStack p_75220_1_, @Nonnull ItemStack p_75220_2_) {
+ delegate.onSlotChange(p_75220_1_, p_75220_2_);
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack onTake(@Nonnull EntityPlayer thePlayer, @Nonnull ItemStack stack) {
+ return delegate.onTake(thePlayer, stack);
+ }
+
+ @Override
+ public boolean isItemValid(@Nonnull ItemStack stack) {
+ return delegate.isItemValid(stack);
+ }
+
+ @Override
+ public boolean getHasStack() {
+ return delegate.getHasStack();
+ }
+
+ @Override
+ public void putStack(@Nonnull ItemStack stack) {
+ delegate.putStack(stack);
+ }
+
+ @Override
+ public void onSlotChanged() {
+ delegate.onSlotChanged();
+ }
+
+ @Override
+ public int getSlotStackLimit() {
+ return delegate.getSlotStackLimit();
+ }
+
+ @Override
+ public int getItemStackLimit(@Nonnull ItemStack stack) {
+ return delegate.getItemStackLimit(stack);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ @Nullable
+ public String getSlotTexture() {
+ return delegate.getSlotTexture();
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack decrStackSize(int amount) {
+ return delegate.decrStackSize(amount);
+ }
+
+ @Override
+ public boolean isHere(@Nonnull IInventory inv, int slotIn) {
+ return delegate.isHere(inv, slotIn);
+ }
+
+ @Override
+ public boolean canTakeStack(@Nonnull EntityPlayer playerIn) {
+ return delegate.canTakeStack(playerIn);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public boolean isEnabled() {
+ return delegate.isEnabled();
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ @Nonnull
+ public ResourceLocation getBackgroundLocation() {
+ return delegate.getBackgroundLocation();
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public void setBackgroundLocation(@Nonnull ResourceLocation texture) {
+ delegate.setBackgroundLocation(texture);
+ }
+
+ @Override
+ public void setBackgroundName(@Nullable String name) {
+ delegate.setBackgroundName(name);
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ @Nullable
+ public TextureAtlasSprite getBackgroundSprite() {
+ return delegate.getBackgroundSprite();
+ }
+
+ @Override
+ public int getSlotIndex() {
+ return delegate.getSlotIndex();
+ }
+
+ @Override
+ public boolean isSameInventory(@Nonnull Slot other) {
+ return delegate.isSameInventory(other);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/loader/ChannelLoader.java b/src/main/java/com/glodblock/github/loader/ChannelLoader.java
new file mode 100644
index 000000000..ec3db5434
--- /dev/null
+++ b/src/main/java/com/glodblock/github/loader/ChannelLoader.java
@@ -0,0 +1,19 @@
+package com.glodblock.github.loader;
+
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.network.*;
+import net.minecraftforge.fml.relauncher.Side;
+
+public class ChannelLoader implements Runnable {
+
+ @Override
+ public void run() {
+ int id = 0;
+ FluidCraft.proxy.netHandler.registerMessage(new CPacketSwitchGuis.Handler(), CPacketSwitchGuis.class, id ++, Side.SERVER);
+ FluidCraft.proxy.netHandler.registerMessage(new CPacketDumpTank.Handler(), CPacketDumpTank.class, id ++, Side.SERVER);
+ FluidCraft.proxy.netHandler.registerMessage(new CPacketTransposeFluid.Handler(), CPacketTransposeFluid.class, id ++, Side.SERVER);
+ FluidCraft.proxy.netHandler.registerMessage(new CPacketEncodePattern.Handler(), CPacketEncodePattern.class, id ++, Side.SERVER);
+ FluidCraft.proxy.netHandler.registerMessage(new CPacketLoadPattern.Handler(), CPacketLoadPattern.class, id ++, Side.SERVER);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/loader/FCBlocks.java b/src/main/java/com/glodblock/github/loader/FCBlocks.java
new file mode 100644
index 000000000..14fde4b5d
--- /dev/null
+++ b/src/main/java/com/glodblock/github/loader/FCBlocks.java
@@ -0,0 +1,33 @@
+package com.glodblock.github.loader;
+
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.common.block.*;
+import com.glodblock.github.handler.RegistryHandler;
+import com.glodblock.github.util.NameConst;
+import net.minecraftforge.fml.common.registry.GameRegistry;
+
+public class FCBlocks {
+
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_FLUID_DISCRETIZER)
+ public static BlockFluidDiscretizer FLUID_DISCRETIZER;
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_FLUID_PATTERN_ENCODER)
+ public static BlockFluidPatternEncoder FLUID_PATTERN_ENCODER;
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_FLUID_PACKET_DECODER)
+ public static BlockFluidPacketDecoder FLUID_PACKET_DECODER;
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_INGREDIENT_BUFFER)
+ public static BlockIngredientBuffer INGREDIENT_BUFFER;
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_BURETTE)
+ public static BlockBurette BURETTE;
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.BLOCK_DUAL_INTERFACE)
+ public static BlockDualInterface DUAL_INTERFACE;
+
+ public static void init(RegistryHandler regHandler) {
+ regHandler.block(NameConst.BLOCK_FLUID_DISCRETIZER, new BlockFluidDiscretizer());
+ regHandler.block(NameConst.BLOCK_FLUID_PATTERN_ENCODER, new BlockFluidPatternEncoder());
+ regHandler.block(NameConst.BLOCK_FLUID_PACKET_DECODER, new BlockFluidPacketDecoder());
+ regHandler.block(NameConst.BLOCK_INGREDIENT_BUFFER, new BlockIngredientBuffer());
+ regHandler.block(NameConst.BLOCK_BURETTE, new BlockBurette());
+ regHandler.block(NameConst.BLOCK_DUAL_INTERFACE, new BlockDualInterface());
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/loader/FCItems.java b/src/main/java/com/glodblock/github/loader/FCItems.java
new file mode 100644
index 000000000..db116d350
--- /dev/null
+++ b/src/main/java/com/glodblock/github/loader/FCItems.java
@@ -0,0 +1,39 @@
+package com.glodblock.github.loader;
+
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.common.item.*;
+import com.glodblock.github.handler.RegistryHandler;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fml.common.registry.GameRegistry;
+
+public class FCItems {
+
+ public static final CreativeTabs TAB_AE2FC = new CreativeTabs(FluidCraft.MODID) {
+ @Override
+ public ItemStack createIcon() {
+ return new ItemStack(DENSE_ENCODED_PATTERN);
+ }
+ };
+
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_FLUID_DROP)
+ public static ItemFluidDrop FLUID_DROP;
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_FLUID_PACKET)
+ public static ItemFluidPacket FLUID_PACKET;
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_DENSE_ENCODED_PATTERN)
+ public static ItemFluidEncodedPattern DENSE_ENCODED_PATTERN;
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_PART_DUAL_INTERFACE)
+ public static ItemPartDualInterface PART_DUAL_INTERFACE;
+ @GameRegistry.ObjectHolder(FluidCraft.MODID + ":" + NameConst.ITEM_PART_FLUID_PATTERN_TERMINAL)
+ public static ItemPartFluidPatternTerminal PART_FLUID_PATTERN_TERMINAL;
+
+ public static void init(RegistryHandler regHandler) {
+ regHandler.item(NameConst.ITEM_FLUID_DROP, new ItemFluidDrop());
+ regHandler.item(NameConst.ITEM_FLUID_PACKET, new ItemFluidPacket());
+ regHandler.item(NameConst.ITEM_DENSE_ENCODED_PATTERN, new ItemFluidEncodedPattern());
+ regHandler.item(NameConst.ITEM_PART_DUAL_INTERFACE, new ItemPartDualInterface());
+ regHandler.item(NameConst.ITEM_PART_FLUID_PATTERN_TERMINAL, new ItemPartFluidPatternTerminal());
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/network/CPacketDumpTank.java b/src/main/java/com/glodblock/github/network/CPacketDumpTank.java
new file mode 100644
index 000000000..4a4962daa
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/CPacketDumpTank.java
@@ -0,0 +1,50 @@
+package com.glodblock.github.network;
+
+import com.glodblock.github.interfaces.TankDumpable;
+import io.netty.buffer.ByteBuf;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
+import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
+
+import javax.annotation.Nullable;
+
+public class CPacketDumpTank implements IMessage {
+
+ private int index;
+
+ public CPacketDumpTank(int index) {
+ this.index = index;
+ }
+
+ public CPacketDumpTank() {
+ // NO-OP
+ }
+
+ @Override
+ public void toBytes(ByteBuf buf) {
+ buf.writeShort(index);
+ }
+
+ @Override
+ public void fromBytes(ByteBuf buf) {
+ index = buf.readShort();
+ }
+
+ public static class Handler implements IMessageHandler {
+
+ @Nullable
+ @Override
+ public IMessage onMessage(CPacketDumpTank message, MessageContext ctx) {
+ EntityPlayerMP player = ctx.getServerHandler().player;
+ player.getServerWorld().addScheduledTask(() -> {
+ if (player.openContainer instanceof TankDumpable) {
+ ((TankDumpable)player.openContainer).dumpTank(message.index);
+ }
+ });
+ return null;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/network/CPacketEncodePattern.java b/src/main/java/com/glodblock/github/network/CPacketEncodePattern.java
new file mode 100644
index 000000000..56e083efa
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/CPacketEncodePattern.java
@@ -0,0 +1,40 @@
+package com.glodblock.github.network;
+
+import com.glodblock.github.client.container.ContainerFluidPatternEncoder;
+import io.netty.buffer.ByteBuf;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
+import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
+
+import javax.annotation.Nullable;
+
+public class CPacketEncodePattern implements IMessage {
+
+ @Override
+ public void toBytes(ByteBuf buf) {
+ // NO-OP
+ }
+
+ @Override
+ public void fromBytes(ByteBuf buf) {
+ // NO-OP
+ }
+
+ public static class Handler implements IMessageHandler {
+
+ @Nullable
+ @Override
+ public IMessage onMessage(CPacketEncodePattern message, MessageContext ctx) {
+ EntityPlayerMP player = ctx.getServerHandler().player;
+ player.getServerWorld().addScheduledTask(() -> {
+ if (player.openContainer instanceof ContainerFluidPatternEncoder) {
+ ((ContainerFluidPatternEncoder)player.openContainer).encodePattern();
+ }
+ });
+ return null;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/network/CPacketLoadPattern.java b/src/main/java/com/glodblock/github/network/CPacketLoadPattern.java
new file mode 100644
index 000000000..b0a04c4dd
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/CPacketLoadPattern.java
@@ -0,0 +1,87 @@
+package com.glodblock.github.network;
+
+import appeng.api.storage.data.IAEItemStack;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.interfaces.PatternConsumer;
+import io.netty.buffer.ByteBuf;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
+import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
+
+import javax.annotation.Nullable;
+import java.io.IOException;
+
+public class CPacketLoadPattern implements IMessage {
+
+ private IAEItemStack[] crafting, output;
+
+ public CPacketLoadPattern(IAEItemStack[] crafting, IAEItemStack[] output) {
+ this.crafting = crafting;
+ this.output = output;
+ }
+
+ public CPacketLoadPattern() {
+ // NO-OP
+ }
+
+ @Override
+ public void toBytes(ByteBuf buf) {
+ writeStacksWithNulls(buf, crafting);
+ writeStacksWithNulls(buf, output);
+ }
+
+ @Override
+ public void fromBytes(ByteBuf buf) {
+ crafting = readStacksWithNulls(buf);
+ output = readStacksWithNulls(buf);
+ }
+
+ private static IAEItemStack[] readStacksWithNulls(ByteBuf buf) {
+ IAEItemStack[] stacks = new IAEItemStack[buf.readByte()];
+ int mask = buf.readInt();
+ for (int i = 0; i < stacks.length; i++) {
+ if ((mask & (1 << i)) != 0) {
+ stacks[i] = AEItemStack.fromPacket(buf);
+ }
+ }
+ return stacks;
+ }
+
+ private static void writeStacksWithNulls(ByteBuf buf, IAEItemStack[] stacks) {
+ buf.writeByte(stacks.length);
+ int mask = 0;
+ for (int i = 0; i < stacks.length; i++) {
+ if (stacks[i] != null) {
+ mask |= 1 << i;
+ }
+ }
+ buf.writeInt(mask);
+ for (IAEItemStack stack : stacks) {
+ try {
+ if (stack != null) {
+ stack.writeToPacket(buf);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to write AE item stack!", e);
+ }
+ }
+ }
+
+ public static class Handler implements IMessageHandler {
+
+ @Nullable
+ @Override
+ public IMessage onMessage(CPacketLoadPattern message, MessageContext ctx) {
+ EntityPlayerMP player = ctx.getServerHandler().player;
+ player.getServerWorld().addScheduledTask(() -> {
+ if (player.openContainer instanceof PatternConsumer) {
+ ((PatternConsumer)player.openContainer).acceptPattern(message.crafting, message.output);
+ }
+ });
+ return null;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/network/CPacketSwitchGuis.java b/src/main/java/com/glodblock/github/network/CPacketSwitchGuis.java
new file mode 100644
index 000000000..6aec08dab
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/CPacketSwitchGuis.java
@@ -0,0 +1,64 @@
+package com.glodblock.github.network;
+
+import appeng.container.AEBaseContainer;
+import appeng.container.ContainerOpenContext;
+import com.glodblock.github.inventory.GuiType;
+import com.glodblock.github.inventory.InventoryHandler;
+import io.netty.buffer.ByteBuf;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.inventory.Container;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
+import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
+
+import javax.annotation.Nullable;
+
+public class CPacketSwitchGuis implements IMessage {
+
+ private GuiType guiType;
+
+ public CPacketSwitchGuis(GuiType guiType) {
+ this.guiType = guiType;
+ }
+
+ public CPacketSwitchGuis() {
+ // NO-OP
+ }
+
+ @Override
+ public void fromBytes(ByteBuf byteBuf) {
+ guiType = GuiType.getByOrdinal(byteBuf.readByte());
+ }
+
+ @Override
+ public void toBytes(ByteBuf byteBuf) {
+ byteBuf.writeByte(guiType != null ? guiType.ordinal() : 0);
+ }
+
+ public static class Handler implements IMessageHandler {
+ @Nullable
+ @Override
+ public IMessage onMessage(CPacketSwitchGuis message, MessageContext ctx) {
+ if (message.guiType == null) {
+ return null;
+ }
+ EntityPlayerMP player = ctx.getServerHandler().player;
+ Container cont = player.openContainer;
+ if (!(cont instanceof AEBaseContainer)) {
+ return null;
+ }
+ ContainerOpenContext context = ((AEBaseContainer)cont).getOpenContext();
+ if (context == null) {
+ return null;
+ }
+ TileEntity te = context.getTile();
+ if (te == null) {
+ return null;
+ }
+ InventoryHandler.openGui(player, player.world, te.getPos(), context.getSide().getFacing(), message.guiType);
+ return null;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/network/CPacketTransposeFluid.java b/src/main/java/com/glodblock/github/network/CPacketTransposeFluid.java
new file mode 100644
index 000000000..dbafb30f3
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/CPacketTransposeFluid.java
@@ -0,0 +1,53 @@
+package com.glodblock.github.network;
+
+import com.glodblock.github.client.container.ContainerBurette;
+import io.netty.buffer.ByteBuf;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
+import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
+import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
+
+import javax.annotation.Nullable;
+
+public class CPacketTransposeFluid implements IMessage {
+
+ private int amount;
+ private boolean into;
+
+ public CPacketTransposeFluid(int amount, boolean into) {
+ this.amount = amount;
+ this.into = into;
+ }
+
+ public CPacketTransposeFluid() {
+ // NO-OP
+ }
+
+ @Override
+ public void toBytes(ByteBuf buf) {
+ buf.writeInt(amount).writeBoolean(into);
+ }
+
+ @Override
+ public void fromBytes(ByteBuf buf) {
+ amount = buf.readInt();
+ into = buf.readBoolean();
+ }
+
+ public static class Handler implements IMessageHandler {
+
+ @Nullable
+ @Override
+ public IMessage onMessage(CPacketTransposeFluid message, MessageContext ctx) {
+ EntityPlayerMP player = ctx.getServerHandler().player;
+ player.getServerWorld().addScheduledTask(() -> {
+ if (player.openContainer instanceof ContainerBurette) {
+ ((ContainerBurette)player.openContainer).tryTransferFluid(message.amount, message.into);
+ }
+ });
+ return null;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/proxy/ClientProxy.java b/src/main/java/com/glodblock/github/proxy/ClientProxy.java
new file mode 100644
index 000000000..873e15589
--- /dev/null
+++ b/src/main/java/com/glodblock/github/proxy/ClientProxy.java
@@ -0,0 +1,66 @@
+package com.glodblock.github.proxy;
+
+import appeng.api.util.AEColor;
+import com.glodblock.github.client.render.DropColourHandler;
+import com.glodblock.github.client.render.RenderIngredientBuffer;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.common.tile.TileIngredientBuffer;
+import com.glodblock.github.handler.ClientRegistryHandler;
+import com.glodblock.github.handler.RegistryHandler;
+import com.glodblock.github.integration.pauto.PackagedFluidCrafting;
+import com.glodblock.github.loader.FCItems;
+import net.minecraft.client.Minecraft;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fml.client.registry.ClientRegistry;
+import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
+
+public class ClientProxy extends CommonProxy {
+
+ private final DropColourHandler dropColourHandler = new DropColourHandler();
+
+ @Override
+ public RegistryHandler createRegistryHandler() {
+ return new ClientRegistryHandler();
+ }
+
+ @Override
+ public void preInit(FMLPreInitializationEvent event){
+ super.preInit(event);
+ MinecraftForge.EVENT_BUS.register(dropColourHandler);
+ ClientRegistry.bindTileEntitySpecialRenderer(TileIngredientBuffer.class, new RenderIngredientBuffer());
+ }
+
+ @Override
+ protected void initPackagedAutoIntegration() {
+ super.initPackagedAutoIntegration();
+ PackagedFluidCrafting.initClient();
+ }
+
+ @Override
+ public void init(FMLInitializationEvent event){
+ super.init(event);
+ Minecraft.getMinecraft().getItemColors().registerItemColorHandler((s, i) -> {
+ FluidStack fluid = ItemFluidDrop.getFluidStack(s);
+ return fluid != null ? dropColourHandler.getColour(fluid) : -1;
+ }, FCItems.FLUID_DROP);
+ Minecraft.getMinecraft().getItemColors().registerItemColorHandler((s, i) -> {
+ if (i == 0) {
+ return -1;
+ }
+ FluidStack fluid = ItemFluidPacket.getFluidStack(s);
+ return fluid != null ? fluid.getFluid().getColor(fluid) : -1;
+ }, FCItems.FLUID_PACKET);
+ Minecraft.getMinecraft().getItemColors().registerItemColorHandler((s, i) -> AEColor.TRANSPARENT.getVariantByTintIndex(i), FCItems.PART_FLUID_PATTERN_TERMINAL);
+
+ }
+
+ @Override
+ public void postInit(FMLPostInitializationEvent event){
+ super.postInit(event);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/proxy/CommonProxy.java b/src/main/java/com/glodblock/github/proxy/CommonProxy.java
new file mode 100644
index 000000000..ea0dde14d
--- /dev/null
+++ b/src/main/java/com/glodblock/github/proxy/CommonProxy.java
@@ -0,0 +1,86 @@
+package com.glodblock.github.proxy;
+
+import appeng.api.AEApi;
+import appeng.api.definitions.IItemDefinition;
+import appeng.core.AppEng;
+import appeng.core.features.ItemDefinition;
+import appeng.recipes.game.DisassembleRecipe;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.common.tile.*;
+import com.glodblock.github.handler.RegistryHandler;
+import com.glodblock.github.integration.pauto.PackagedFluidCrafting;
+import com.glodblock.github.inventory.InventoryHandler;
+import com.glodblock.github.loader.ChannelLoader;
+import com.glodblock.github.loader.FCBlocks;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.util.Ae2Reflect;
+import com.glodblock.github.util.ModAndClassUtil;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.item.Item;
+import net.minecraft.item.crafting.IRecipe;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.fml.common.Loader;
+import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
+import net.minecraftforge.fml.common.network.NetworkRegistry;
+import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper;
+import net.minecraftforge.fml.common.registry.ForgeRegistries;
+import net.minecraftforge.fml.common.registry.GameRegistry;
+import net.minecraftforge.fml.relauncher.Side;
+
+import java.util.Objects;
+
+public class CommonProxy {
+
+ public final RegistryHandler regHandler = createRegistryHandler();
+ public final SimpleNetworkWrapper netHandler = NetworkRegistry.INSTANCE.newSimpleChannel(FluidCraft.MODID);
+
+ public RegistryHandler createRegistryHandler() {
+ return new RegistryHandler();
+ }
+
+ public void preInit(FMLPreInitializationEvent event) {
+ MinecraftForge.EVENT_BUS.register(regHandler);
+ FCBlocks.init(regHandler);
+ FCItems.init(regHandler);
+ GameRegistry.registerTileEntity(TileFluidDiscretizer.class, FluidCraft.resource(NameConst.BLOCK_FLUID_DISCRETIZER));
+ GameRegistry.registerTileEntity(TileFluidPatternEncoder.class, FluidCraft.resource(NameConst.BLOCK_FLUID_PATTERN_ENCODER));
+ GameRegistry.registerTileEntity(TileFluidPacketDecoder.class, FluidCraft.resource(NameConst.BLOCK_FLUID_PACKET_DECODER));
+ GameRegistry.registerTileEntity(TileIngredientBuffer.class, FluidCraft.resource(NameConst.BLOCK_INGREDIENT_BUFFER));
+ GameRegistry.registerTileEntity(TileBurette.class, FluidCraft.resource(NameConst.BLOCK_BURETTE));
+ GameRegistry.registerTileEntity(TileDualInterface.class, FluidCraft.resource(NameConst.BLOCK_DUAL_INTERFACE));
+ (new ChannelLoader()).run();
+ if (ModAndClassUtil.AUTO_P) {
+ initPackagedAutoIntegration();
+ }
+ }
+
+ protected void initPackagedAutoIntegration() {
+ PackagedFluidCrafting.init();
+ }
+
+ public void init(FMLInitializationEvent event) {
+ regHandler.onInit();
+ IRecipe disassembleRecipe = ForgeRegistries.RECIPES.getValue(new ResourceLocation(AppEng.MOD_ID, "disassemble"));
+ if (disassembleRecipe instanceof DisassembleRecipe) {
+ Ae2Reflect.getDisassemblyNonCellMap((DisassembleRecipe)disassembleRecipe).put(
+ createItemDefn(FCItems.DENSE_ENCODED_PATTERN),
+ AEApi.instance().definitions().materials().blankPattern());
+ }
+ }
+
+ public void postInit(FMLPostInitializationEvent event) {
+ NetworkRegistry.INSTANCE.registerGuiHandler(FluidCraft.INSTANCE, new InventoryHandler());
+ }
+
+ private static IItemDefinition createItemDefn(Item item) {
+ return new ItemDefinition(Objects.requireNonNull(item.getRegistryName()).toString(), item);
+ }
+
+ public SimpleNetworkWrapper getNetHandler() {
+ return netHandler;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/util/Ae2Reflect.java b/src/main/java/com/glodblock/github/util/Ae2Reflect.java
new file mode 100644
index 000000000..ed01916fa
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/Ae2Reflect.java
@@ -0,0 +1,112 @@
+package com.glodblock.github.util;
+
+import appeng.api.definitions.IItemDefinition;
+import appeng.container.implementations.ContainerPatternTerm;
+import appeng.container.slot.OptionalSlotFake;
+import appeng.container.slot.SlotFakeCraftingMatrix;
+import appeng.container.slot.SlotRestrictedInput;
+import appeng.recipes.game.DisassembleRecipe;
+import appeng.util.inv.ItemSlot;
+import net.minecraft.inventory.Container;
+import net.minecraft.inventory.InventoryCrafting;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Map;
+
+public class Ae2Reflect {
+
+ private static final Method mItemSlot_setExtractable;
+ private static final Field fDisassembleRecipe_nonCellMappings;
+ private static final Field fContainerPatternTerm_craftingSlots;
+ private static final Field fContainerPatternTerm_outputSlots;
+ private static final Field fContainerPatternTerm_patternSlotIN;
+ private static final Field fContainerPatternTerm_patternSlotOUT;
+ private static final Field fInventory_container;
+
+ static {
+ try {
+ mItemSlot_setExtractable = reflectMethod(ItemSlot.class, "setExtractable", boolean.class);
+ fInventory_container = reflectField(InventoryCrafting.class, "eventHandler", "field_70465_c", "c");
+ fDisassembleRecipe_nonCellMappings = reflectField(DisassembleRecipe.class, "nonCellMappings");
+ fContainerPatternTerm_craftingSlots = reflectField(ContainerPatternTerm.class, "craftingSlots");
+ fContainerPatternTerm_outputSlots = reflectField(ContainerPatternTerm.class, "outputSlots");
+ fContainerPatternTerm_patternSlotIN = reflectField(ContainerPatternTerm.class, "patternSlotIN");
+ fContainerPatternTerm_patternSlotOUT = reflectField(ContainerPatternTerm.class, "patternSlotOUT");
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to initialize AE2 reflection hacks!", e);
+ }
+ }
+
+ public static Method reflectMethod(Class> owner, String name, Class>... paramTypes) throws NoSuchMethodException {
+ Method m = owner.getDeclaredMethod(name, paramTypes);
+ m.setAccessible(true);
+ return m;
+ }
+
+ public static Field reflectField(Class> owner, String ...names) throws NoSuchFieldException {
+ Field f = null;
+ for (String name : names) {
+ try {
+ f = owner.getDeclaredField(name);
+ if (f != null) break;
+ }
+ catch (NoSuchFieldException ignore) {
+ }
+ }
+ if (f == null) throw new NoSuchFieldException("Can't find field from " + Arrays.toString(names));
+ f.setAccessible(true);
+ return f;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T readField(Object owner, Field field) {
+ try {
+ return (T)field.get(owner);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to read field: " + field);
+ }
+ }
+
+ public static void writeField(Object owner, Field field, Object value) {
+ try {
+ field.set(owner, value);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to write field: " + field);
+ }
+ }
+
+ public static Container getCraftContainer(InventoryCrafting inv) {
+ return Ae2Reflect.readField(inv, fInventory_container);
+ }
+
+ public static void setItemSlotExtractable(ItemSlot slot, boolean extractable) {
+ try {
+ mItemSlot_setExtractable.invoke(slot, extractable);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to invoke method: " + mItemSlot_setExtractable, e);
+ }
+ }
+
+ public static Map getDisassemblyNonCellMap(DisassembleRecipe recipe) {
+ return readField(recipe, fDisassembleRecipe_nonCellMappings);
+ }
+
+ public static SlotFakeCraftingMatrix[] getCraftingSlots(ContainerPatternTerm cont) {
+ return readField(cont, fContainerPatternTerm_craftingSlots);
+ }
+
+ public static OptionalSlotFake[] getOutputSlots(ContainerPatternTerm cont) {
+ return readField(cont, fContainerPatternTerm_outputSlots);
+ }
+
+ public static SlotRestrictedInput getPatternSlotIn(ContainerPatternTerm cont) {
+ return readField(cont, fContainerPatternTerm_patternSlotIN);
+ }
+
+ public static SlotRestrictedInput getPatternSlotOut(ContainerPatternTerm cont) {
+ return readField(cont, fContainerPatternTerm_patternSlotOUT);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/util/Ae2ReflectClient.java b/src/main/java/com/glodblock/github/util/Ae2ReflectClient.java
new file mode 100644
index 000000000..2d6078a46
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/Ae2ReflectClient.java
@@ -0,0 +1,90 @@
+package com.glodblock.github.util;
+
+import appeng.client.gui.AEBaseGui;
+import appeng.client.gui.implementations.*;
+import appeng.client.gui.widgets.GuiTabButton;
+import appeng.client.render.StackSizeRenderer;
+import appeng.fluids.client.gui.GuiFluidInterface;
+import com.glodblock.github.client.container.ContainerFluidPatternTerminal;
+import com.google.common.collect.ImmutableMap;
+import net.minecraft.client.renderer.block.model.IBakedModel;
+import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
+import net.minecraftforge.common.model.TRSRTransformation;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+
+@SuppressWarnings("unchecked")
+public class Ae2ReflectClient {
+
+ private static final Field fAEBaseGui_stackSizeRenderer;
+ private static final Constructor extends IBakedModel> cItemEncodedPatternBakedModel;
+ private static final Field fGuiPriority_originalGuiBtn;
+ private static final Field fGuiCraftingStatus_originalGuiBtn;
+ private static final Field fGuiPatternTerm_container;
+ private static final Field fGuiMEMonitorable_monitorableContainer;
+ private static final Field fGuiMEMonitorable_configSrc;
+ private static final Field fGuiMEMonitorable_craftingStatusBtn;
+ private static final Field fGuiInterface_priority;
+ private static final Field fGuiFluidInterface_priority;
+
+ static {
+ try {
+ fAEBaseGui_stackSizeRenderer = Ae2Reflect.reflectField(AEBaseGui.class, "stackSizeRenderer");
+ cItemEncodedPatternBakedModel = (Constructor extends IBakedModel>)Class
+ .forName("appeng.client.render.crafting.ItemEncodedPatternBakedModel")
+ .getDeclaredConstructor(IBakedModel.class, ImmutableMap.class);
+ cItemEncodedPatternBakedModel.setAccessible(true);
+ fGuiPriority_originalGuiBtn = Ae2Reflect.reflectField(GuiPriority.class, "originalGuiBtn");
+ fGuiCraftingStatus_originalGuiBtn = Ae2Reflect.reflectField(GuiCraftingStatus.class, "originalGuiBtn");
+ fGuiPatternTerm_container = Ae2Reflect.reflectField(GuiPatternTerm.class, "container");
+ fGuiMEMonitorable_monitorableContainer = Ae2Reflect.reflectField(GuiMEMonitorable.class, "monitorableContainer");
+ fGuiMEMonitorable_configSrc = Ae2Reflect.reflectField(GuiMEMonitorable.class, "configSrc");
+ fGuiMEMonitorable_craftingStatusBtn = Ae2Reflect.reflectField(GuiMEMonitorable.class, "craftingStatusBtn");
+ fGuiInterface_priority = Ae2Reflect.reflectField(GuiInterface.class, "priority");
+ fGuiFluidInterface_priority = Ae2Reflect.reflectField(GuiFluidInterface.class, "priority");
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to initialize AE2 reflection hacks!", e);
+ }
+ }
+
+ public static StackSizeRenderer getStackSizeRenderer(AEBaseGui gui) {
+ return Ae2Reflect.readField(gui, fAEBaseGui_stackSizeRenderer);
+ }
+
+ public static IBakedModel bakeEncodedPatternModel(IBakedModel baseModel,
+ ImmutableMap transforms) {
+ try {
+ return cItemEncodedPatternBakedModel.newInstance(baseModel, transforms);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to invoke constructor: " + cItemEncodedPatternBakedModel, e);
+ }
+ }
+
+ public static GuiTabButton getOriginalGuiButton(GuiPriority gui) {
+ return Ae2Reflect.readField(gui, fGuiPriority_originalGuiBtn);
+ }
+
+ public static GuiTabButton getOriginalGuiButton(GuiCraftingStatus gui) {
+ return Ae2Reflect.readField(gui, fGuiCraftingStatus_originalGuiBtn);
+ }
+
+ public static void setGuiContainer(GuiPatternTerm instance, ContainerFluidPatternTerminal container) {
+ Ae2Reflect.writeField(instance, fGuiPatternTerm_container, container);
+ Ae2Reflect.writeField(instance, fGuiMEMonitorable_monitorableContainer, container);
+ Ae2Reflect.writeField(instance, fGuiMEMonitorable_configSrc, container.getConfigManager());
+ }
+
+ public static GuiTabButton getCraftingStatusButton(GuiMEMonitorable gui) {
+ return Ae2Reflect.readField(gui, fGuiMEMonitorable_craftingStatusBtn);
+ }
+
+ public static GuiTabButton getPriorityButton(GuiInterface gui) {
+ return Ae2Reflect.readField(gui, fGuiInterface_priority);
+ }
+
+ public static GuiTabButton getPriorityButton(GuiFluidInterface gui) {
+ return Ae2Reflect.readField(gui, fGuiFluidInterface_priority);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/util/FluidKey.java b/src/main/java/com/glodblock/github/util/FluidKey.java
new file mode 100644
index 000000000..f09dbc390
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/FluidKey.java
@@ -0,0 +1,38 @@
+package com.glodblock.github.util;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+public class FluidKey {
+
+ private final Fluid fluid;
+ @Nullable
+ private final NBTTagCompound tag;
+
+ public FluidKey(Fluid fluid, @Nullable NBTTagCompound tag) {
+ this.fluid = fluid;
+ this.tag = tag;
+ }
+
+ public FluidKey(FluidStack fluid) {
+ this(fluid.getFluid(), fluid.tag);
+ }
+
+ @Override
+ public int hashCode() {
+ return fluid.getName().hashCode() ^ (tag != null ? Integer.rotateLeft(tag.hashCode(), 17) : 0x4e6f5467); // NoTg
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof FluidKey)) {
+ return false;
+ }
+ return fluid.getName().equals(((FluidKey)obj).fluid.getName()) && Objects.equals(tag, ((FluidKey)obj).tag);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/util/FluidPatternDetails.java b/src/main/java/com/glodblock/github/util/FluidPatternDetails.java
new file mode 100644
index 000000000..8fdf53528
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/FluidPatternDetails.java
@@ -0,0 +1,199 @@
+package com.glodblock.github.util;
+
+import appeng.api.networking.crafting.ICraftingPatternDetails;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.util.item.AEItemStack;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.Constants;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class FluidPatternDetails implements ICraftingPatternDetails, Comparable {
+
+ private final ItemStack patternStack;
+ private IAEItemStack patternStackAe;
+ private IAEItemStack[] inputs = null, inputsCond = null, outputs = null, outputsCond = null;
+ private int priority = 0;
+
+ public FluidPatternDetails(ItemStack stack) {
+ this.patternStack = stack;
+ this.patternStackAe = Objects.requireNonNull(AEItemStack.fromItemStack(stack)); // s2g
+ }
+
+ @Override
+ public ItemStack getPattern() {
+ return patternStack;
+ }
+
+ @Override
+ public int getPriority() {
+ return priority;
+ }
+
+ @Override
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ @Override
+ public boolean isCraftable() {
+ return false;
+ }
+
+ @Override
+ public boolean canSubstitute() {
+ return false;
+ }
+
+ @Override
+ public IAEItemStack[] getInputs() {
+ return checkInitialized(inputs);
+ }
+
+ @Override
+ public IAEItemStack[] getCondensedInputs() {
+ return checkInitialized(inputsCond);
+ }
+
+ public boolean setInputs(IAEItemStack[] inputs) {
+ for (IAEItemStack stack : inputs) { // see note at top of class
+ if (stack == null) {
+ return false;
+ }
+ }
+ IAEItemStack[] condensed = condenseStacks(inputs);
+ if (condensed.length == 0) {
+ return false;
+ }
+ this.inputs = inputs;
+ this.inputsCond = condensed;
+ return true;
+ }
+
+ @Override
+ public IAEItemStack[] getOutputs() {
+ return checkInitialized(outputs);
+ }
+
+ @Override
+ public IAEItemStack[] getCondensedOutputs() {
+ return checkInitialized(outputsCond);
+ }
+
+ public boolean setOutputs(IAEItemStack[] outputs) {
+ for (IAEItemStack stack : outputs) { // see note at top of class
+ if (stack == null) {
+ return false;
+ }
+ }
+ IAEItemStack[] condensed = condenseStacks(outputs);
+ if (condensed.length == 0) {
+ return false;
+ }
+ this.outputs = outputs;
+ this.outputsCond = condensed;
+ return true;
+ }
+
+ private static IAEItemStack[] condenseStacks(IAEItemStack[] stacks) {
+ // AE item stacks are equivalent iff they are of the same item type (not accounting for stack size)
+ // thus, it's not the semantically-correct definition of "equal" but it's useful for matching item types
+ Map accMap = new HashMap<>();
+ for (IAEItemStack stack : stacks) {
+ if (stack != null) {
+ IAEItemStack acc = accMap.get(stack);
+ if (acc == null) {
+ accMap.put(stack, stack.copy());
+ } else {
+ acc.add(stack);
+ }
+ }
+ }
+ return accMap.values().toArray(new IAEItemStack[0]);
+ }
+
+ @Override
+ public ItemStack getOutput(InventoryCrafting craftingInv, World world) {
+ throw new IllegalStateException("Not a crafting recipe!");
+ }
+
+ @Override
+ public boolean isValidItemForSlot(int slotIndex, ItemStack itemStack, World world) {
+ throw new IllegalStateException("Not a crafting recipe!");
+ }
+
+ private static T checkInitialized(@Nullable T value) {
+ if (value == null) {
+ throw new IllegalStateException("Pattern is not initialized!");
+ }
+ return value;
+ }
+
+ @Override
+ public int hashCode() {
+ return patternStackAe.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // ae2 null-checks the pattern stack here for some reason, but doesn't null-check in hashCode()
+ // this is inconsistent, so i've just decided to assert non-null in the constructor, which is to say that
+ // the pattern stack can never be null here
+ return obj instanceof FluidPatternDetails && patternStackAe.equals(((FluidPatternDetails)obj).patternStackAe);
+ }
+
+ @Override
+ public int compareTo(FluidPatternDetails o) {
+ return Integer.compare(o.priority, this.priority);
+ }
+
+ public ItemStack writeToStack() {
+ NBTTagCompound tag = new NBTTagCompound();
+ tag.setTag("Inputs", writeStackArray(checkInitialized(inputs)));
+ tag.setTag("Outputs", writeStackArray(checkInitialized(outputs)));
+ patternStack.setTagCompound(tag);
+ patternStackAe = Objects.requireNonNull(AEItemStack.fromItemStack(patternStack));
+ return patternStack;
+ }
+
+ public static NBTTagList writeStackArray(IAEItemStack[] stacks) {
+ NBTTagList listTag = new NBTTagList();
+ for (IAEItemStack stack : stacks) {
+ if (stack != null) {
+ // see note at top of class
+ NBTTagCompound stackTag = new NBTTagCompound();
+ stack.writeToNBT(stackTag);
+ listTag.appendTag(stackTag);
+ }
+ }
+ return listTag;
+ }
+
+ public boolean readFromStack() {
+ if (!patternStack.hasTagCompound()) {
+ return false;
+ }
+ NBTTagCompound tag = Objects.requireNonNull(patternStack.getTagCompound());
+ // may be possible to enter a partially-correct state if setInputs succeeds but setOutputs failed
+ // but outside code should treat it as completely incorrect and not attempt to make calls
+ return setInputs(readStackArray(tag.getTagList("Inputs", Constants.NBT.TAG_COMPOUND), 9))
+ && setOutputs(readStackArray(tag.getTagList("Outputs", Constants.NBT.TAG_COMPOUND), 3));
+ }
+
+ public static IAEItemStack[] readStackArray(NBTTagList listTag, int maxCount) {
+ // see note at top of class
+ IAEItemStack[] stacks = new IAEItemStack[Math.min(listTag.tagCount(), maxCount)];
+ for (int i = 0; i < stacks.length; i++) {
+ stacks[i] = AEItemStack.fromNBT(listTag.getCompoundTagAt(i));
+ }
+ return stacks;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/util/ModAndClassUtil.java b/src/main/java/com/glodblock/github/util/ModAndClassUtil.java
new file mode 100644
index 000000000..20bee1e24
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/ModAndClassUtil.java
@@ -0,0 +1,15 @@
+package com.glodblock.github.util;
+
+import net.minecraftforge.fml.common.Loader;
+
+public final class ModAndClassUtil {
+
+ public static boolean AUTO_P = false;
+
+ public static void init() {
+ if (Loader.isModLoaded("packagedauto")) {
+ AUTO_P = true;
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/util/MouseRegionManager.java b/src/main/java/com/glodblock/github/util/MouseRegionManager.java
new file mode 100644
index 000000000..14ac897f9
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/MouseRegionManager.java
@@ -0,0 +1,82 @@
+package com.glodblock.github.util;
+
+import net.minecraft.client.audio.PositionedSoundRecord;
+import net.minecraft.client.gui.inventory.GuiContainer;
+import net.minecraft.init.SoundEvents;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MouseRegionManager {
+
+ private final GuiContainer gui;
+ private final List regions = new ArrayList<>();
+
+ public MouseRegionManager(GuiContainer gui) {
+ this.gui = gui;
+ }
+
+ public void addRegion(int x, int y, int width, int height, Handler handler) {
+ regions.add(new Region(x, y, width, height, handler));
+ }
+
+ public boolean onClick(int mX, int mY, int button) {
+ mX -= gui.getGuiLeft();
+ mY -= gui.getGuiTop();
+ for (Region region : regions) {
+ if (region.containsMouse(mX, mY) && region.handler.onClick(button)) {
+ gui.mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(SoundEvents.UI_BUTTON_CLICK, 1F));
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void render(int mX, int mY) {
+ mX -= gui.getGuiLeft();
+ mY -= gui.getGuiTop();
+ for (Region region : regions) {
+ if (region.containsMouse(mX, mY)) {
+ List tooltip = region.handler.getTooltip();
+ if (tooltip != null) {
+ gui.drawHoveringText(tooltip, mX, mY);
+ return;
+ }
+ }
+ }
+ }
+
+ private static class Region {
+
+ private final int x, y, width, height;
+ private final Handler handler;
+
+ Region(int x, int y, int width, int height, Handler handler) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.handler = handler;
+ }
+
+ boolean containsMouse(int mX, int mY) {
+ return mX >= x && mX < x + width && mY >= y && mY < y + height;
+ }
+
+ }
+
+ public interface Handler {
+
+ @Nullable
+ default List getTooltip() {
+ return null;
+ }
+
+ default boolean onClick(int button) {
+ return false;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/util/NameConst.java b/src/main/java/com/glodblock/github/util/NameConst.java
new file mode 100644
index 000000000..2cf43fa5b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/NameConst.java
@@ -0,0 +1,41 @@
+package com.glodblock.github.util;
+
+import com.glodblock.github.FluidCraft;
+import net.minecraft.util.ResourceLocation;
+
+public final class NameConst {
+
+ public static final String BLOCK_FLUID_DISCRETIZER = "fluid_discretizer";
+ public static final String BLOCK_FLUID_PATTERN_ENCODER = "fluid_pattern_encoder";
+ public static final String BLOCK_FLUID_PACKET_DECODER = "fluid_packet_decoder";
+ public static final String BLOCK_INGREDIENT_BUFFER = "ingredient_buffer";
+ public static final String BLOCK_BURETTE = "burette";
+ public static final String BLOCK_DUAL_INTERFACE = "dual_interface";
+
+ public static final String ITEM_FLUID_DROP = "fluid_drop";
+ public static final String ITEM_FLUID_PACKET = "fluid_packet";
+ public static final String ITEM_DENSE_ENCODED_PATTERN = "dense_encoded_pattern";
+ public static final String ITEM_PART_DUAL_INTERFACE = "part_dual_interface";
+ public static final String ITEM_PART_FLUID_PATTERN_TERMINAL = "part_fluid_pattern_terminal";
+
+ public static final String TT_KEY = FluidCraft.MODID + ".tooltip.";
+ public static final String TT_FLUID_PACKET = TT_KEY + "fluid_packet";
+ public static final String TT_INVALID_FLUID = TT_KEY + "invalid_fluid";
+ public static final String TT_PROCESSING_RECIPE_ONLY = TT_KEY + "processing_recipe_only";
+ public static final String TT_CRAFTING_RECIPE_ONLY = TT_KEY + "crafting_recipe_only";
+ public static final String TT_ENCODE_PATTERN = TT_KEY + "encode_pattern";
+ public static final String TT_EMPTY = TT_KEY + "empty";
+ public static final String TT_DUMP_TANK = TT_KEY + "dump_tank";
+ public static final String TT_TRANSPOSE_IN = TT_KEY + "transpose_in";
+ public static final String TT_TRANSPOSE_OUT = TT_KEY + "transpose_out";
+
+ private static final String GUI_KEY = FluidCraft.MODID + ".gui.";
+ public static final String GUI_FLUID_PATTERN_ENCODER = GUI_KEY + BLOCK_FLUID_PATTERN_ENCODER;
+ public static final String GUI_FLUID_PACKET_DECODER = GUI_KEY + BLOCK_FLUID_PACKET_DECODER;
+ public static final String GUI_INGREDIENT_BUFFER = GUI_KEY + BLOCK_INGREDIENT_BUFFER;
+ public static final String GUI_BURETTE = GUI_KEY + BLOCK_BURETTE;
+
+ public static final ResourceLocation MODEL_DENSE_ENCODED_PATTERN = FluidCraft.resource("builtin/dense_encoded_pattern");
+ public static final ResourceLocation MODEL_FLUID_PACKET = FluidCraft.resource("builtin/fluid_packet");
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/util/SetBackedMachineSet.java b/src/main/java/com/glodblock/github/util/SetBackedMachineSet.java
new file mode 100644
index 000000000..3b645a2a9
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/SetBackedMachineSet.java
@@ -0,0 +1,61 @@
+package com.glodblock.github.util;
+
+import appeng.api.networking.IGridHost;
+import appeng.api.networking.IGridNode;
+import appeng.api.networking.IMachineSet;
+
+import javax.annotation.Nonnull;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+
+public class SetBackedMachineSet implements IMachineSet {
+
+ private final Class extends IGridHost> machineClass;
+ private final Set backingSet;
+
+ public SetBackedMachineSet(Class extends IGridHost> machineClass, Set backingSet) {
+ this.machineClass = machineClass;
+ this.backingSet = backingSet;
+ }
+
+ @Nonnull
+ @Override
+ public Class extends IGridHost> getMachineClass() {
+ return machineClass;
+ }
+
+ @Override
+ public int size() {
+ return backingSet.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return backingSet.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ //noinspection SuspiciousMethodCalls
+ return backingSet.contains(o);
+ }
+
+ @Override
+ @Nonnull
+ public Iterator iterator() {
+ return backingSet.iterator();
+ }
+
+ @Override
+ public void forEach(Consumer super IGridNode> action) {
+ backingSet.forEach(action);
+ }
+
+ @Override
+ public Spliterator spliterator() {
+ return backingSet.spliterator();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/ae2fc/blockstates/burette.json b/src/main/resources/assets/ae2fc/blockstates/burette.json
new file mode 100644
index 000000000..d1ed555f3
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/burette.json
@@ -0,0 +1,7 @@
+{
+ "variants": {
+ "normal": {
+ "model": "ae2fc:burette"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/blockstates/dual_interface.json b/src/main/resources/assets/ae2fc/blockstates/dual_interface.json
new file mode 100644
index 000000000..40f5dd3d9
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/dual_interface.json
@@ -0,0 +1,35 @@
+{
+ "forge_marker": 1,
+ "variants": {
+ "omnidirectional": {
+ "true": {
+ "model": "ae2fc:dual_interface"
+ },
+ "false": {
+ "model": "ae2fc:dual_interface_oriented"
+ }
+ },
+ "facing": {
+ "east": {
+ "x": 90,
+ "y": 90
+ },
+ "west": {
+ "x": 90,
+ "y": 270
+ },
+ "south": {
+ "x": 270
+ },
+ "north": {
+ "x": 90
+ },
+ "up": {
+ "x": 0
+ },
+ "down": {
+ "x": 180
+ }
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/blockstates/fluid_discretizer.json b/src/main/resources/assets/ae2fc/blockstates/fluid_discretizer.json
new file mode 100644
index 000000000..2aece3ea9
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/fluid_discretizer.json
@@ -0,0 +1,7 @@
+{
+ "variants": {
+ "normal": {
+ "model": "ae2fc:fluid_discretizer"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/blockstates/fluid_packet_decoder.json b/src/main/resources/assets/ae2fc/blockstates/fluid_packet_decoder.json
new file mode 100644
index 000000000..e008e19aa
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/fluid_packet_decoder.json
@@ -0,0 +1,7 @@
+{
+ "variants": {
+ "normal": {
+ "model": "ae2fc:fluid_packet_decoder"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/blockstates/fluid_pattern_encoder.json b/src/main/resources/assets/ae2fc/blockstates/fluid_pattern_encoder.json
new file mode 100644
index 000000000..28ddc775c
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/fluid_pattern_encoder.json
@@ -0,0 +1,7 @@
+{
+ "variants": {
+ "normal": {
+ "model": "ae2fc:fluid_pattern_encoder"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json b/src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json
new file mode 100644
index 000000000..15db1e22f
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json
@@ -0,0 +1,7 @@
+{
+ "variants": {
+ "normal": {
+ "model": "ae2fc:ingredient_buffer"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/lang/en_us.lang b/src/main/resources/assets/ae2fc/lang/en_us.lang
new file mode 100644
index 000000000..0234a8b97
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/lang/en_us.lang
@@ -0,0 +1,33 @@
+itemGroup.ae2fc=AE2 Fluid Crafting
+
+tile.ae2fc:fluid_discretizer.name=ME Fluid Discretizer
+tile.ae2fc:fluid_pattern_encoder.name=Fluid Pattern Encoder
+tile.ae2fc:fluid_packet_decoder.name=ME Fluid Packet Decoder
+tile.ae2fc:ingredient_buffer.name=Ingredient Buffer
+tile.ae2fc:burette.name=Precision Burette
+tile.ae2fc:dual_interface.name=ME Dual Interface
+tile.ae2fc:part_dual_interface.name=ME Dual Interface
+
+item.ae2fc:fluid_drop.name=Drop of %s
+item.ae2fc:fluid_packet.name=Fluid Packet
+item.ae2fc:dense_encoded_pattern.name=Encoded Pattern
+item.ae2fc:part_dual_interface.name=ME Dual Interface
+item.ae2fc:part_fluid_pattern_terminal.name=ME Fluid Pattern Terminal
+
+ae2fc.gui.fluid_pattern_encoder=Fluid Pattern Encoder
+ae2fc.gui.fluid_packet_decoder=ME Fluid Packet Decoder
+ae2fc.gui.ingredient_buffer=Ingredient Buffer
+ae2fc.gui.burette=Precision Burette
+
+ae2fc.tooltip.fluid_packet=Use a Fluid Packet Decoder to\nconvert this back into fluid.
+ae2fc.tooltip.invalid_fluid=Invalid fluid!
+ae2fc.tooltip.processing_recipe_only=Not a processing recipe!
+ae2fc.tooltip.crafting_recipe_only=Not a crafting recipe!
+ae2fc.tooltip.encode_pattern=Encode Pattern
+ae2fc.tooltip.empty=Empty
+ae2fc.tooltip.dump_tank=Dump Tank Contents
+ae2fc.tooltip.transpose_in=Transfer Into Tank
+ae2fc.tooltip.transpose_out=Transfer Into Item
+
+ae2fc.pauto.fluid_processing.name=Fluid Processing
+ae2fc.pauto.fluid_processing.name_short=Fluid
diff --git a/src/main/resources/assets/ae2fc/models/block/burette.json b/src/main/resources/assets/ae2fc/models/block/burette.json
new file mode 100644
index 000000000..4c773d93b
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/burette.json
@@ -0,0 +1,12 @@
+{
+ "parent": "block/cube",
+ "textures": {
+ "particle": "ae2fc:blocks/burette_top",
+ "down": "ae2fc:blocks/burette_top",
+ "up": "ae2fc:blocks/burette_top",
+ "north": "ae2fc:blocks/burette_side",
+ "east": "ae2fc:blocks/burette_side",
+ "south": "ae2fc:blocks/burette_side",
+ "west": "ae2fc:blocks/burette_side"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/block/dual_interface.json b/src/main/resources/assets/ae2fc/models/block/dual_interface.json
new file mode 100644
index 000000000..c118eccff
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/dual_interface.json
@@ -0,0 +1,6 @@
+{
+ "parent": "block/cube_all",
+ "textures": {
+ "all": "ae2fc:blocks/interface"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/block/dual_interface_oriented.json b/src/main/resources/assets/ae2fc/models/block/dual_interface_oriented.json
new file mode 100644
index 000000000..72db67793
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/dual_interface_oriented.json
@@ -0,0 +1,12 @@
+{
+ "parent": "block/cube",
+ "textures": {
+ "particle": "ae2fc:blocks/interface",
+ "down": "ae2fc:blocks/interface_alternate",
+ "up": "ae2fc:blocks/interface",
+ "north": "ae2fc:blocks/interface_alternate_arrow",
+ "south": "ae2fc:blocks/interface_alternate_arrow",
+ "east": "ae2fc:blocks/interface_alternate_arrow",
+ "west": "ae2fc:blocks/interface_alternate_arrow"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/block/fluid_discretizer.json b/src/main/resources/assets/ae2fc/models/block/fluid_discretizer.json
new file mode 100644
index 000000000..fea0d3f8d
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/fluid_discretizer.json
@@ -0,0 +1,6 @@
+{
+ "parent": "block/cube_all",
+ "textures": {
+ "all": "ae2fc:blocks/fluid_discretizer"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/block/fluid_packet_decoder.json b/src/main/resources/assets/ae2fc/models/block/fluid_packet_decoder.json
new file mode 100644
index 000000000..622135b4d
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/fluid_packet_decoder.json
@@ -0,0 +1,6 @@
+{
+ "parent": "block/cube_all",
+ "textures": {
+ "all": "ae2fc:blocks/fluid_packet_decoder"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/block/fluid_pattern_encoder.json b/src/main/resources/assets/ae2fc/models/block/fluid_pattern_encoder.json
new file mode 100644
index 000000000..3452f91b9
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/fluid_pattern_encoder.json
@@ -0,0 +1,12 @@
+{
+ "parent": "block/cube",
+ "textures": {
+ "particle": "ae2fc:blocks/fluid_pattern_encoder_top",
+ "down": "ae2fc:blocks/fluid_pattern_encoder_bottom",
+ "up": "ae2fc:blocks/fluid_pattern_encoder_top",
+ "north": "ae2fc:blocks/fluid_pattern_encoder_side",
+ "east": "ae2fc:blocks/fluid_pattern_encoder_side",
+ "south": "ae2fc:blocks/fluid_pattern_encoder_side",
+ "west": "ae2fc:blocks/fluid_pattern_encoder_side"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/block/ingredient_buffer.json b/src/main/resources/assets/ae2fc/models/block/ingredient_buffer.json
new file mode 100644
index 000000000..b5b2fb4d3
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/ingredient_buffer.json
@@ -0,0 +1,6 @@
+{
+ "parent": "block/cube_all",
+ "textures": {
+ "all": "ae2fc:blocks/ingredient_buffer"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/burette.json b/src/main/resources/assets/ae2fc/models/item/burette.json
new file mode 100644
index 000000000..b4f5de04a
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/burette.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2fc:block/burette"
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/dense_encoded_pattern.json b/src/main/resources/assets/ae2fc/models/item/dense_encoded_pattern.json
new file mode 100644
index 000000000..41c52e0a6
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/dense_encoded_pattern.json
@@ -0,0 +1,6 @@
+{
+ "parent": "item/generated",
+ "textures": {
+ "layer0": "ae2fc:items/dense_encoded_pattern"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/dual_interface.json b/src/main/resources/assets/ae2fc/models/item/dual_interface.json
new file mode 100644
index 000000000..24aed2604
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/dual_interface.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2fc:block/dual_interface"
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_discretizer.json b/src/main/resources/assets/ae2fc/models/item/fluid_discretizer.json
new file mode 100644
index 000000000..54ada676d
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_discretizer.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2fc:block/fluid_discretizer"
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_drop.json b/src/main/resources/assets/ae2fc/models/item/fluid_drop.json
new file mode 100644
index 000000000..ca55bf1da
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_drop.json
@@ -0,0 +1,6 @@
+{
+ "parent": "item/generated",
+ "textures": {
+ "layer0": "ae2fc:items/fluid_drop"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_packet.json b/src/main/resources/assets/ae2fc/models/item/fluid_packet.json
new file mode 100644
index 000000000..134d55d6f
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_packet.json
@@ -0,0 +1,7 @@
+{
+ "parent": "item/generated",
+ "textures": {
+ "layer0": "ae2fc:items/fluid_packet",
+ "layer1": "ae2fc:items/fluid_packet_overlay"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_packet_decoder.json b/src/main/resources/assets/ae2fc/models/item/fluid_packet_decoder.json
new file mode 100644
index 000000000..b34f43ede
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_packet_decoder.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2fc:block/fluid_packet_decoder"
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_pattern_encoder.json b/src/main/resources/assets/ae2fc/models/item/fluid_pattern_encoder.json
new file mode 100644
index 000000000..2d6ab9fd0
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_pattern_encoder.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2fc:block/fluid_pattern_encoder"
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/ingredient_buffer.json b/src/main/resources/assets/ae2fc/models/item/ingredient_buffer.json
new file mode 100644
index 000000000..8e58f24ca
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/ingredient_buffer.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2fc:block/ingredient_buffer"
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/part_dual_interface.json b/src/main/resources/assets/ae2fc/models/item/part_dual_interface.json
new file mode 100644
index 000000000..e4a14e799
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/part_dual_interface.json
@@ -0,0 +1,106 @@
+{
+ "parent": "appliedenergistics2:item/part/part",
+ "textures": {
+ "sides": "appliedenergistics2:parts/monitor_sides",
+ "back": "appliedenergistics2:parts/monitor_back",
+ "front": "ae2fc:blocks/interface"
+ },
+ "elements": [
+ {
+ "from": [
+ 2,
+ 2,
+ 7
+ ],
+ "to": [
+ 14,
+ 14,
+ 9
+ ],
+ "faces": {
+ "north": {
+ "texture": "#front"
+ },
+ "south": {
+ "texture": "#back"
+ },
+ "east": {
+ "texture": "#sides"
+ },
+ "west": {
+ "texture": "#sides"
+ },
+ "up": {
+ "texture": "#sides"
+ },
+ "down": {
+ "texture": "#sides"
+ }
+ }
+ },
+ {
+ "from": [
+ 5,
+ 5,
+ 10
+ ],
+ "to": [
+ 11,
+ 11,
+ 11
+ ],
+ "faces": {
+ "north": {
+ "texture": "#front"
+ },
+ "south": {
+ "texture": "#back"
+ },
+ "east": {
+ "texture": "#sides"
+ },
+ "west": {
+ "texture": "#sides"
+ },
+ "up": {
+ "texture": "#sides"
+ },
+ "down": {
+ "texture": "#sides"
+ }
+ }
+ },
+ {
+ "from": [
+ 5,
+ 5,
+ 9
+ ],
+ "to": [
+ 11,
+ 11,
+ 10
+ ],
+ "faces": {
+ "north": {
+ "texture": "#front"
+ },
+ "south": {
+ "texture": "#back"
+ },
+ "east": {
+ "texture": "#sides"
+ },
+ "west": {
+ "texture": "#sides"
+ },
+ "up": {
+ "texture": "#sides"
+ },
+ "down": {
+ "texture": "#sides"
+ }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json
new file mode 100644
index 000000000..d0516315d
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json
@@ -0,0 +1,9 @@
+{
+ "parent": "appliedenergistics2:item/part/display",
+ "textures": {
+ "front": "appliedenergistics2:items/part/pattern_terminal",
+ "front_bright": "ae2fc:parts/pattern_terminal_bright",
+ "front_medium": "ae2fc:parts/pattern_terminal_medium",
+ "front_dark": "ae2fc:parts/pattern_terminal_dark"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/part/f_pattern_term_off.json b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_off.json
new file mode 100644
index 000000000..217560bcb
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_off.json
@@ -0,0 +1,8 @@
+{
+ "parent": "appliedenergistics2:part/display_off",
+ "textures": {
+ "lightsBright": "ae2fc:parts/pattern_terminal_bright",
+ "lightsMedium": "ae2fc:parts/pattern_terminal_medium",
+ "lightsDark": "ae2fc:parts/pattern_terminal_dark"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/models/part/f_pattern_term_on.json b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_on.json
new file mode 100644
index 000000000..124b40516
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_on.json
@@ -0,0 +1,76 @@
+{
+ "ae2_uvl_marker": true,
+ "textures": {
+ "lightsBright": "ae2fc:parts/pattern_terminal_bright",
+ "lightsMedium": "ae2fc:parts/pattern_terminal_medium",
+ "lightsDark": "ae2fc:parts/pattern_terminal_dark"
+ },
+ "elements": [
+ {
+ "from": [
+ 2,
+ 2,
+ 0
+ ],
+ "to": [
+ 14,
+ 14,
+ 2
+ ],
+ "faces": {
+ "north": {
+ "texture": "#lightsBright",
+ "tintindex": 3,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ }
+ }
+ },
+ {
+ "from": [
+ 2,
+ 2,
+ 0
+ ],
+ "to": [
+ 14,
+ 14,
+ 2
+ ],
+ "faces": {
+ "north": {
+ "texture": "#lightsMedium",
+ "tintindex": 2,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ }
+ }
+ },
+ {
+ "from": [
+ 2,
+ 2,
+ 0
+ ],
+ "to": [
+ 14,
+ 14,
+ 2
+ ],
+ "faces": {
+ "north": {
+ "texture": "#lightsDark",
+ "tintindex": 1,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/ae2fc/models/part/interface_base.json b/src/main/resources/assets/ae2fc/models/part/interface_base.json
new file mode 100644
index 000000000..685f8f0b2
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/interface_base.json
@@ -0,0 +1,104 @@
+{
+ "textures": {
+ "sides": "appliedenergistics2:parts/export_bus_sides",
+ "sidesStatus": "appliedenergistics2:parts/monitor_sides_status",
+ "back": "appliedenergistics2:parts/monitor_back",
+ "front": "ae2fc:blocks/interface",
+ "particle": "appliedenergistics2:parts/monitor_back"
+ },
+ "elements": [
+ {
+ "from": [
+ 2,
+ 2,
+ 0
+ ],
+ "to": [
+ 14,
+ 14,
+ 2
+ ],
+ "faces": {
+ "down": {
+ "texture": "#sides"
+ },
+ "up": {
+ "texture": "#sides"
+ },
+ "south": {
+ "texture": "#back"
+ },
+ "east": {
+ "texture": "#sides"
+ },
+ "north": {
+ "texture": "#front"
+ },
+ "west": {
+ "texture": "#sides"
+ }
+ }
+ },
+ {
+ "from": [
+ 5,
+ 5,
+ 3
+ ],
+ "to": [
+ 11,
+ 11,
+ 4
+ ],
+ "faces": {
+ "down": {
+ "texture": "#sides"
+ },
+ "up": {
+ "texture": "#sides"
+ },
+ "south": {
+ "texture": "#back"
+ },
+ "east": {
+ "texture": "#sides"
+ },
+ "north": {
+ "texture": "#front"
+ },
+ "west": {
+ "texture": "#sides"
+ }
+ }
+ },
+ {
+ "from": [
+ 5,
+ 5,
+ 2
+ ],
+ "to": [
+ 11,
+ 11,
+ 3
+ ],
+ "faces": {
+ "down": {
+ "texture": "#sidesStatus"
+ },
+ "up": {
+ "texture": "#sidesStatus"
+ },
+ "south": {
+ "texture": "#back"
+ },
+ "east": {
+ "texture": "#sidesStatus"
+ },
+ "west": {
+ "texture": "#sidesStatus"
+ }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/ae2fc/models/part/interface_has_channel.json b/src/main/resources/assets/ae2fc/models/part/interface_has_channel.json
new file mode 100644
index 000000000..f47216bf2
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/interface_has_channel.json
@@ -0,0 +1,54 @@
+{
+ "ae2_uvl_marker": true,
+ "textures": {
+ "indicator": "appliedenergistics2:parts/monitor_sides_status_has_channel"
+ },
+ "elements": [
+ {
+ "from": [
+ 5,
+ 5,
+ 2
+ ],
+ "to": [
+ 11,
+ 11,
+ 3
+ ],
+ "faces": {
+ "down": {
+ "texture": "#indicator",
+ "tintindex": 1,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ },
+ "up": {
+ "texture": "#indicator",
+ "tintindex": 1,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ },
+ "east": {
+ "texture": "#indicator",
+ "tintindex": 1,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ },
+ "west": {
+ "texture": "#indicator",
+ "tintindex": 1,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/ae2fc/models/part/interface_off.json b/src/main/resources/assets/ae2fc/models/part/interface_off.json
new file mode 100644
index 000000000..3ab8507fa
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/interface_off.json
@@ -0,0 +1,33 @@
+{
+ "textures": {
+ "indicator": "appliedenergistics2:parts/monitor_sides_status_off"
+ },
+ "elements": [
+ {
+ "from": [
+ 5,
+ 5,
+ 2
+ ],
+ "to": [
+ 11,
+ 11,
+ 3
+ ],
+ "faces": {
+ "down": {
+ "texture": "#indicator"
+ },
+ "up": {
+ "texture": "#indicator"
+ },
+ "east": {
+ "texture": "#indicator"
+ },
+ "west": {
+ "texture": "#indicator"
+ }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/ae2fc/models/part/interface_on.json b/src/main/resources/assets/ae2fc/models/part/interface_on.json
new file mode 100644
index 000000000..cd41b8f73
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/interface_on.json
@@ -0,0 +1,54 @@
+{
+ "ae2_uvl_marker": true,
+ "textures": {
+ "indicator": "appliedenergistics2:parts/monitor_sides_status_on"
+ },
+ "elements": [
+ {
+ "from": [
+ 5,
+ 5,
+ 2
+ ],
+ "to": [
+ 11,
+ 11,
+ 3
+ ],
+ "faces": {
+ "down": {
+ "texture": "#indicator",
+ "tintindex": 3,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ },
+ "up": {
+ "texture": "#indicator",
+ "tintindex": 3,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ },
+ "east": {
+ "texture": "#indicator",
+ "tintindex": 3,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ },
+ "west": {
+ "texture": "#indicator",
+ "tintindex": 3,
+ "uvlightmap": {
+ "sky": 0.007,
+ "block": 0.007
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/ae2fc/recipes/burette.json b/src/main/resources/assets/ae2fc/recipes/burette.json
new file mode 100644
index 000000000..6c52cd1bc
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/recipes/burette.json
@@ -0,0 +1,31 @@
+{
+ "type": "forge:ore_shaped",
+ "result": {
+ "item": "ae2fc:burette",
+ "count": 1
+ },
+ "pattern": [
+ "ihi",
+ "gbg",
+ "ici"
+ ],
+ "key": {
+ "i": {
+ "type": "forge:ore_dict",
+ "ore": "ingotIron"
+ },
+ "h": {
+ "item": "minecraft:hopper"
+ },
+ "g": {
+ "item": "appliedenergistics2:quartz_glass"
+ },
+ "b": {
+ "item": "minecraft:bucket"
+ },
+ "c": {
+ "type": "appliedenergistics2:part",
+ "part": "material.calculation_processor"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/recipes/dual_interface.json b/src/main/resources/assets/ae2fc/recipes/dual_interface.json
new file mode 100644
index 000000000..fca5492aa
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/recipes/dual_interface.json
@@ -0,0 +1,15 @@
+{
+ "type": "minecraft:crafting_shapeless",
+ "ingredients": [
+ {
+ "item": "appliedenergistics2:interface"
+ },
+ {
+ "item": "appliedenergistics2:fluid_interface"
+ }
+ ],
+ "result": {
+ "item": "ae2fc:dual_interface",
+ "nbt": {}
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/recipes/dual_interface_alter.json b/src/main/resources/assets/ae2fc/recipes/dual_interface_alter.json
new file mode 100644
index 000000000..a68c62def
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/recipes/dual_interface_alter.json
@@ -0,0 +1,11 @@
+{
+ "type": "minecraft:crafting_shapeless",
+ "ingredients": [
+ {
+ "item": "ae2fc:part_dual_interface"
+ }
+ ],
+ "result": {
+ "item": "ae2fc:dual_interface"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/recipes/fluid_discretizer.json b/src/main/resources/assets/ae2fc/recipes/fluid_discretizer.json
new file mode 100644
index 000000000..1684ef4f0
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/recipes/fluid_discretizer.json
@@ -0,0 +1,33 @@
+{
+ "type": "forge:ore_shaped",
+ "result": {
+ "item": "ae2fc:fluid_discretizer",
+ "count": 1
+ },
+ "pattern": [
+ "ipi",
+ "smt",
+ "ipi"
+ ],
+ "key": {
+ "i": {
+ "type": "forge:ore_dict",
+ "ore": "ingotIron"
+ },
+ "p": {
+ "type": "appliedenergistics2:part",
+ "part": "material.engineering_processor"
+ },
+ "s": {
+ "type": "appliedenergistics2:part",
+ "part": "part.fluid_storage_bus"
+ },
+ "m": {
+ "item": "appliedenergistics2:condenser"
+ },
+ "t": {
+ "type": "appliedenergistics2:part",
+ "part": "part.storage_bus"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/recipes/fluid_packet_decoder.json b/src/main/resources/assets/ae2fc/recipes/fluid_packet_decoder.json
new file mode 100644
index 000000000..1b5155c9c
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/recipes/fluid_packet_decoder.json
@@ -0,0 +1,32 @@
+{
+ "type": "forge:ore_shaped",
+ "result": {
+ "item": "ae2fc:fluid_packet_decoder",
+ "count": 1
+ },
+ "pattern": [
+ "ihi",
+ "cfc",
+ "ipi"
+ ],
+ "key": {
+ "i": {
+ "type": "forge:ore_dict",
+ "ore": "ingotIron"
+ },
+ "h": {
+ "item": "minecraft:hopper"
+ },
+ "c": {
+ "type": "appliedenergistics2:part",
+ "part": "cable_glass.fluix"
+ },
+ "f": {
+ "item": "appliedenergistics2:fluid_interface"
+ },
+ "p": {
+ "type": "appliedenergistics2:part",
+ "part": "material.calculation_processor"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/recipes/fluid_pattern_encoder.json b/src/main/resources/assets/ae2fc/recipes/fluid_pattern_encoder.json
new file mode 100644
index 000000000..cec58226c
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/recipes/fluid_pattern_encoder.json
@@ -0,0 +1,30 @@
+{
+ "type": "forge:ore_shaped",
+ "result": {
+ "item": "ae2fc:fluid_pattern_encoder",
+ "count": 1
+ },
+ "pattern": [
+ "lpl",
+ "iwi",
+ "iii"
+ ],
+ "key": {
+ "l": {
+ "type": "forge:ore_dict",
+ "ore": "blockLapis"
+ },
+ "p": {
+ "type": "appliedenergistics2:part",
+ "part": "material.engineering_processor"
+ },
+ "i": {
+ "type": "forge:ore_dict",
+ "ore": "ingotIron"
+ },
+ "w": {
+ "type": "forge:ore_dict",
+ "ore": "workbench"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/recipes/ingredient_buffer.json b/src/main/resources/assets/ae2fc/recipes/ingredient_buffer.json
new file mode 100644
index 000000000..f08fc818b
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/recipes/ingredient_buffer.json
@@ -0,0 +1,37 @@
+{
+ "type": "forge:ore_shaped",
+ "result": {
+ "item": "ae2fc:ingredient_buffer",
+ "count": 1
+ },
+ "pattern": [
+ "ili",
+ "agf",
+ "isi"
+ ],
+ "key": {
+ "i": {
+ "type": "forge:ore_dict",
+ "ore": "ingotIron"
+ },
+ "g": {
+ "item": "appliedenergistics2:quartz_glass"
+ },
+ "l": {
+ "type": "appliedenergistics2:part",
+ "part": "material.cell1k_part"
+ },
+ "a": {
+ "type": "appliedenergistics2:part",
+ "part": "material.annihilation_core"
+ },
+ "f": {
+ "type": "appliedenergistics2:part",
+ "part": "material.formation_core"
+ },
+ "s": {
+ "type": "appliedenergistics2:part",
+ "part": "material.fluid_cell1k_part"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/recipes/part_dual_interface.json b/src/main/resources/assets/ae2fc/recipes/part_dual_interface.json
new file mode 100644
index 000000000..dc5d91f7b
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/recipes/part_dual_interface.json
@@ -0,0 +1,11 @@
+{
+ "type": "minecraft:crafting_shapeless",
+ "ingredients": [
+ {
+ "item": "ae2fc:dual_interface"
+ }
+ ],
+ "result": {
+ "item": "ae2fc:part_dual_interface"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/recipes/part_fluid_pattern_terminal.json b/src/main/resources/assets/ae2fc/recipes/part_fluid_pattern_terminal.json
new file mode 100644
index 000000000..bd2103a91
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/recipes/part_fluid_pattern_terminal.json
@@ -0,0 +1,15 @@
+{
+ "type": "minecraft:crafting_shapeless",
+ "ingredients": [
+ {
+ "type": "appliedenergistics2:part",
+ "part": "part.pattern_terminal"
+ },
+ {
+ "item": "ae2fc:fluid_pattern_encoder"
+ }
+ ],
+ "result": {
+ "item": "ae2fc:part_fluid_pattern_terminal"
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/burette_side.png b/src/main/resources/assets/ae2fc/textures/blocks/burette_side.png
new file mode 100644
index 000000000..f633ef09a
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/burette_side.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/burette_top.png b/src/main/resources/assets/ae2fc/textures/blocks/burette_top.png
new file mode 100644
index 000000000..0cff59c3d
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/burette_top.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_discretizer.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_discretizer.png
new file mode 100644
index 000000000..d08e7976c
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/fluid_discretizer.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_packet_decoder.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_packet_decoder.png
new file mode 100644
index 000000000..623fb299b
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/fluid_packet_decoder.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_bottom.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_bottom.png
new file mode 100644
index 000000000..38ca52d36
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_bottom.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_side.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_side.png
new file mode 100644
index 000000000..b2b560145
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_side.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_top.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_top.png
new file mode 100644
index 000000000..967533398
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/fluid_pattern_encoder_top.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/ingredient_buffer.png b/src/main/resources/assets/ae2fc/textures/blocks/ingredient_buffer.png
new file mode 100644
index 000000000..8233dcdcb
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/ingredient_buffer.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/interface.png b/src/main/resources/assets/ae2fc/textures/blocks/interface.png
new file mode 100644
index 000000000..1fd54c43d
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/interface.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate.png b/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate.png
new file mode 100644
index 000000000..d8948d150
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate_arrow.png b/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate_arrow.png
new file mode 100644
index 000000000..f86bc6759
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/interface_alternate_arrow.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/gui/burette.png b/src/main/resources/assets/ae2fc/textures/gui/burette.png
new file mode 100644
index 000000000..7238bf637
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/gui/burette.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/gui/fluid_packet_decoder.png b/src/main/resources/assets/ae2fc/textures/gui/fluid_packet_decoder.png
new file mode 100644
index 000000000..4e17ee57f
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/gui/fluid_packet_decoder.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/gui/fluid_pattern_encoder.png b/src/main/resources/assets/ae2fc/textures/gui/fluid_pattern_encoder.png
new file mode 100644
index 000000000..045611f80
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/gui/fluid_pattern_encoder.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/gui/ingredient_buffer.png b/src/main/resources/assets/ae2fc/textures/gui/ingredient_buffer.png
new file mode 100644
index 000000000..e1d351945
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/gui/ingredient_buffer.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/items/dense_encoded_pattern.png b/src/main/resources/assets/ae2fc/textures/items/dense_encoded_pattern.png
new file mode 100644
index 000000000..1a19d070b
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/items/dense_encoded_pattern.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/items/fluid_drop.png b/src/main/resources/assets/ae2fc/textures/items/fluid_drop.png
new file mode 100644
index 000000000..32ae84ab4
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/items/fluid_drop.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/items/fluid_packet.png b/src/main/resources/assets/ae2fc/textures/items/fluid_packet.png
new file mode 100644
index 000000000..bb301cdbe
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/items/fluid_packet.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/items/fluid_packet_overlay.png b/src/main/resources/assets/ae2fc/textures/items/fluid_packet_overlay.png
new file mode 100644
index 000000000..6610c0fe5
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/items/fluid_packet_overlay.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_bright.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_bright.png
new file mode 100644
index 000000000..11e59a01d
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_bright.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_dark.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_dark.png
new file mode 100644
index 000000000..44e4c64ff
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_dark.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_medium.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_medium.png
new file mode 100644
index 000000000..a9271aa88
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_medium.png differ
diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info
new file mode 100644
index 000000000..2e1f9cd4a
--- /dev/null
+++ b/src/main/resources/mcmod.info
@@ -0,0 +1,18 @@
+[
+ {
+ "modid": "ae2fc",
+ "name": "AE2 Fluid Crafting",
+ "description": "Lets you do Crafting... with Fluids!",
+ "version": "${version}",
+ "mcversion": "${mcversion}",
+ "url": "",
+ "updateUrl": "",
+ "authorList": ["GlodBlock"],
+ "credits":"phantamanta44",
+ "logoFile": "",
+ "screenshots": [],
+ "useDependencyInformation": true,
+ "dependencies": ["appliedenergistics2"],
+ "requiredMods": ["appliedenergistics2"]
+ }
+]
\ No newline at end of file
diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta
new file mode 100644
index 000000000..5744378fb
--- /dev/null
+++ b/src/main/resources/pack.mcmeta
@@ -0,0 +1,6 @@
+{
+ "pack": {
+ "description": "Cool Mod",
+ "pack_format": 3
+ }
+}