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..44062dc5f
--- /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..1f1fa328c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+
+Port with permission from phantamanta44.
+
+# AE2 Fluid Crafting Rework
+
+[![Downloads](https://cf.way2muchnoise.eu/full_623955_downloads.svg)](https://www.curseforge.com/minecraft/mc-mods/ae2-fluid-crafting-rework) ![MCVsrsion](https://cf.way2muchnoise.eu/versions/623955.svg)
+
+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.16, 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)
+
+## Features
+
+ - You can code fluid patterns on fluid terminal directly.
+ - Mult/Div/Add/Sub Button also works on fluid.
+ - An extended fluid terminal with 16 inputs and 4 outputs.
+ - You can add or decrease fluid's amount by clicking with container.
+ - Fluid Pattern can display its contents when put on pattern terminal.
+ - [1.12.2] Fix the fluid amount display error when removing fluid.
+ - [1.12.2] Upgrade cards can be inserted in Dual Interface.
+ - [1.12.2] Fluid pattern is searchable in interface terminal.
+ - [1.12.2] Fix crash when using block mode, fluid will also be considered as blockable.
+ - [1.12.2] Fluid export bus supports crafting card.
+
+## Installation
+
+### 1.7.10
+Any version of AE2(Both Official AE2 and GTNH edition AE2 works).
+
+**Extra Cells isn't needed**
+
+### 1.12.2
+Unofficial AE2([PAE2](https://www.curseforge.com/minecraft/mc-mods/ae2-extended-life)).
+
+You can directly upgrade origin AE2FC2 in your old save.
+
+Official AE2 isn't supported, you can use origin [AE2FC](https://github.com/phantamanta44/ae2-fluid-crafting) if you are playing with Official AE2.
+
+## How to Use It
+https://github.com/GlodBlock/AE2FluidCraft-Rework/wiki
+
+## Credited Works
+
+E. Geng(@phantamanta44) and KilaBash (@Yefancy) - Their amazing origin work in 1.12.
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..65e2f8de9
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,137 @@
+buildscript {
+ repositories {
+ maven {
+ url 'https://maven.minecraftforge.net/'
+ }
+ maven {
+ name 'Scala CI dependencies'
+ url 'https://repo1.maven.org/maven2/'
+ }
+ maven {
+ name "forge"
+ url "https://files.minecraftforge.net/maven"
+ }
+ maven {
+ name "mixins"
+ url "https://repo.spongepowered.org/repository/maven-public/"
+ }
+ mavenLocal()
+ }
+ dependencies {
+ classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
+ classpath "org.spongepowered:mixingradle:0.7-SNAPSHOT"
+ }
+}
+
+apply plugin: 'net.minecraftforge.gradle'
+apply plugin: 'idea'
+apply plugin: 'org.spongepowered.mixin'
+
+version = "1.0.0-a"
+group = "ae2fc"
+archivesBaseName = "Fluid Craft for AE2"
+
+sourceCompatibility = targetCompatibility = '1.8'
+compileJava {
+ sourceCompatibility = targetCompatibility = '1.8'
+}
+
+
+// Generate a fixed tsrg file after generating the default tsrg file
+createMcpToSrg {
+ outputs.upToDateWhen {false}
+ doLast {
+ fixFG5TsrgForMixinAP(output.get().asFile, file("${buildDir}/fixMcpToSrg/output.tsrg"))
+ }
+}
+
+// Function that actually fixes the TSRG file
+static def fixFG5TsrgForMixinAP(File inFile, File outFile) {
+ // Make directory if needed
+ outFile.parentFile.mkdirs()
+
+ try (Scanner scanner = new Scanner(inFile); PrintWriter out = new PrintWriter(outFile)) {
+ boolean firstLine = true
+ while (scanner.hasNextLine()) {
+ String next = scanner.nextLine()
+
+ // Skip first 'tsrg left right' header line
+ if (firstLine) {
+ firstLine = false
+ continue
+ }
+
+ // Skip 'static' indicators
+ if (next.trim() == "static") {
+ continue
+ }
+
+ // Export line otherwise
+ out.println(next)
+ }
+ }
+}
+
+repositories {
+ maven { url 'https://dvs1.progwml6.com/files/maven/' }
+ maven { url 'https://cursemaven.com/' }
+ maven { url "https://www.cursemaven.com" }
+}
+
+dependencies {
+ minecraft([
+ group : "net.minecraftforge",
+ name : "forge",
+ version: "1.16.5-36.1.17"
+ ])
+ annotationProcessor 'org.spongepowered:mixin:0.8.2:processor'
+ implementation fg.deobf('mezz.jei:jei-1.16.5:7.7.0.98') //jei
+ implementation fg.deobf('curse.maven:ae2-extended-life-223794:3608871') //ae2
+ implementation fg.deobf('curse.maven:packagedauto-308380:4478954') //pauto
+}
+
+minecraft {
+ mappings channel: 'snapshot', version: '20210309-1.16.5'
+ accessTransformer = file('src/main/resources/META-INF/ae2fc_at.cfg')
+ runs {
+ client = {
+ properties "forge.logging.markers": "SCAN,REGISTRIES,REGISTRYDUMP"
+ properties "forge.logging.console.level": "debug"
+ properties "mixin.env.remapRefMap": "true"
+ properties "mixin.env.refMapRemappingFile": "${projectDir}/build/createSrgToMcp/output.srg".toString()
+ workingDirectory project.file("run").canonicalPath
+ arg "-mixin.config=" + "mixins.ae2fc.json"
+ source sourceSets.main
+ }
+ server = {
+ properties "forge.logging.markers": "SCAN,REGISTRIES,REGISTRYDUMP"
+ properties "forge.logging.console.level": "debug"
+ properties "mixin.env.remapRefMap": "true"
+ properties "mixin.env.refMapRemappingFile": "${projectDir}/build/createSrgToMcp/output.srg".toString()
+ workingDirectory project.file("run").canonicalPath
+ arg "-mixin.config=" + "mixins.ae2fc.json"
+ source sourceSets.main
+ }
+ }
+}
+
+jar {
+ manifest {
+ attributes([
+ "Specification-Title": "FluidCraftForAE2",
+ "Specification-Vendor": "GlodBlock",
+ "Specification-Version": "1",
+ "Implementation-Title": "${archivesBaseName}",
+ "Implementation-Version": "${version}",
+ "Implementation-Vendor": "GlodBlock",
+ "TweakClass": "org.spongepowered.asm.launch.MixinTweaker",
+ "TweakOrder": 0,
+ "MixinConfigs": "mixins.ae2fc.json"
+ ])
+ }
+}
+
+mixin {
+ reobfSrgFile = file("${buildDir}/fixMcpToSrg/output.tsrg")
+ add sourceSets.main, "mixins.ae2fc.refmap.json"
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 000000000..e6a23a955
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxPermSize=512m
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..490fda857
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..d06389f3b
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
\ No newline at end of file
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..0ee319350
--- /dev/null
+++ b/src/main/java/com/glodblock/github/FluidCraft.java
@@ -0,0 +1,141 @@
+package com.glodblock.github;
+
+import appeng.api.config.Upgrades;
+import appeng.api.definitions.IItemDefinition;
+import appeng.api.features.AEFeature;
+import appeng.api.util.AEColor;
+import appeng.core.Api;
+import appeng.core.features.ItemDefinition;
+import appeng.recipes.game.DisassembleRecipe;
+import com.glodblock.github.client.model.FluidEncodedPatternModel;
+import com.glodblock.github.client.render.DropColourHandler;
+import com.glodblock.github.client.render.RenderIngredientBuffer;
+import com.glodblock.github.common.block.BlockIngredientBuffer;
+import com.glodblock.github.common.block.BlockLargeIngredientBuffer;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.handler.ClientRegistryHandler;
+import com.glodblock.github.handler.RegistryHandler;
+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.FCUtil;
+import com.glodblock.github.util.ModAndClassUtil;
+import net.minecraft.client.Minecraft;
+import net.minecraft.item.Item;
+import net.minecraft.item.crafting.IRecipeSerializer;
+import net.minecraft.item.crafting.SpecialRecipeSerializer;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.eventbus.api.IEventBus;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fml.DistExecutor;
+import net.minecraftforge.fml.client.registry.ClientRegistry;
+import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
+import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
+import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent;
+import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent;
+import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Objects;
+
+@Mod(FluidCraft.MODID)
+public class FluidCraft {
+
+ public static final String MODID = "ae2fc";
+
+ public static Logger log;
+ public static FluidCraft INSTANCE;
+ public FluidCraft() {
+ assert INSTANCE == null;
+ INSTANCE = this;
+ FCItems.init(RegistryHandler.INSTANCE);
+ FCBlocks.init(RegistryHandler.INSTANCE);
+ IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
+ DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> bus.register(ClientRegistryHandler.INSTANCE));
+ DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> bus.register(DropColourHandler.INSTANCE));
+ bus.register(RegistryHandler.INSTANCE);
+ bus.addListener(this::commonSetup);
+ bus.addListener(this::clientSetup);
+ bus.addListener(this::imcWait);
+ bus.addListener(this::imcProcess);
+ bus.addListener(this::finish);
+ ModAndClassUtil.init();
+ }
+
+ public void commonSetup(FMLCommonSetupEvent event) {
+ log = LogManager.getLogger();
+ ChannelLoader.load();
+ if (ModAndClassUtil.AUTO_P) {
+ //PackagedFluidCrafting.init();
+ }
+ RegistryHandler.INSTANCE.onInit();
+ IRecipeSerializer disassembleRecipe = DisassembleRecipe.SERIALIZER;
+ if (disassembleRecipe instanceof SpecialRecipeSerializer) {
+ Map map = Ae2Reflect.getDisassemblyNonCellMap(
+ (DisassembleRecipe) Ae2Reflect.getRecipeFactory((SpecialRecipeSerializer>)disassembleRecipe).apply(
+ new ResourceLocation("appliedenergistics2", "disassemble")
+ )
+ );
+ map.put(
+ createItemDefn(FCItems.DENSE_ENCODED_PATTERN, AEFeature.PATTERNS),
+ Api.instance().definitions().materials().blankPattern()
+ );
+ }
+ Upgrades.CRAFTING.registerItem(FCBlocks.DUAL_INTERFACE, 1);
+ Upgrades.CRAFTING.registerItem(FCItems.PART_DUAL_INTERFACE, 1);
+ }
+
+ public void clientSetup(FMLClientSetupEvent event) {
+ ClientRegistryHandler.INSTANCE.onInit();
+ ClientRegistry.bindTileEntityRenderer(FCUtil.getTileType(BlockIngredientBuffer.TileIngredientBuffer.class, FCBlocks.INGREDIENT_BUFFER), RenderIngredientBuffer::new);
+ ClientRegistry.bindTileEntityRenderer(FCUtil.getTileType(BlockLargeIngredientBuffer.TileLargeIngredientBuffer.class, FCBlocks.LARGE_INGREDIENT_BUFFER), RenderIngredientBuffer::new);
+
+ Minecraft.getInstance().getItemColors().register((s, i) -> {
+ FluidStack fluid = ItemFluidDrop.getFluidStack(s);
+ return !fluid.isEmpty() ? DropColourHandler.INSTANCE.getColour(fluid) : 0xFFFFFFFF;
+ }, FCItems.FLUID_DROP);
+ Minecraft.getInstance().getItemColors().register((s, i) -> {
+ if (i == 0) {
+ return 0xFFFFFFFF;
+ }
+ FluidStack fluid = ItemFluidPacket.getFluidStack(s);
+ return !fluid.isEmpty() ? fluid.getFluid().getAttributes().getColor(fluid) : 0xFFFFFFFF;
+ }, FCItems.FLUID_PACKET);
+ Minecraft.getInstance().getItemColors().register((s, i) -> AEColor.TRANSPARENT.getVariantByTintIndex(i), FCItems.PART_FLUID_PATTERN_TERMINAL);
+ Minecraft.getInstance().getItemColors().register((s, i) -> AEColor.TRANSPARENT.getVariantByTintIndex(i), FCItems.PART_DUAL_INTERFACE);
+ Minecraft.getInstance().getItemColors().register(FluidEncodedPatternModel.PATTERN_ITEM_COLOR_HANDLER);
+ }
+
+ public void imcWait(InterModEnqueueEvent event) {
+
+ }
+
+ public void imcProcess(InterModEnqueueEvent event) {
+
+ }
+
+ public void finish(FMLLoadCompleteEvent event) {
+
+ }
+
+ private static IItemDefinition createItemDefn(Item item, AEFeature... feature) {
+ return new ItemDefinition(
+ Objects.requireNonNull(item.getRegistryName()).toString(),
+ item,
+ feature.length > 0 ?
+ EnumSet.of(AEFeature.PATTERNS) : EnumSet.noneOf(AEFeature.class)
+ );
+ }
+
+ public static ResourceLocation resource(String id) {
+ return new ResourceLocation(MODID, id);
+ }
+
+}
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..3ece43f5c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiFCPriority.java
@@ -0,0 +1,67 @@
+package com.glodblock.github.client;
+
+import appeng.client.gui.AEBaseScreen;
+import appeng.client.gui.NumberEntryType;
+import appeng.client.gui.implementations.NumberEntryWidget;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.client.gui.widgets.TabButton;
+import appeng.core.sync.network.NetworkHandler;
+import appeng.core.sync.packets.SwitchGuisPacket;
+import com.glodblock.github.client.container.ContainerFCPriority;
+import com.glodblock.github.common.tile.TileDualInterface;
+import com.glodblock.github.loader.FCBlocks;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.network.NetworkManager;
+import com.glodblock.github.network.packets.CPacketFluidCraftBtns;
+import com.mojang.blaze3d.matrix.MatrixStack;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.ItemRenderer;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.text.ITextComponent;
+
+import java.util.OptionalInt;
+
+public class GuiFCPriority extends AEBaseScreen {
+ private final ContainerType> originGui;
+ private final TabButton back;
+ private final NumberEntryWidget priority;
+
+ public GuiFCPriority(ContainerFCPriority container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) {
+ super(container, playerInventory, title, style);
+ this.originGui = container.getPriorityHost().getContainerType();
+ ItemRenderer itemRender = Minecraft.getInstance().getItemRenderer();
+ ItemStack dual;
+ if (container.getPriorityHost() instanceof TileDualInterface) {
+ dual = new ItemStack(FCBlocks.DUAL_INTERFACE);
+ } else {
+ dual = new ItemStack(FCItems.PART_DUAL_INTERFACE);
+ }
+ this.back = new TabButton(dual, dual.getDisplayName(), itemRender, btn ->
+ NetworkHandler.instance().sendToServer(new SwitchGuisPacket(this.originGui)));
+ this.priority = new NumberEntryWidget(NumberEntryType.PRIORITY);
+ this.priority.setTextFieldBounds(62, 57, 50);
+ this.priority.setMinValue(Integer.MIN_VALUE);
+ this.priority.setValue(this.container.getPriorityValue());
+ this.priority.setOnChange(this::savePriority);
+ this.priority.setOnConfirm(() -> {
+ this.savePriority();
+ this.back.onPress();
+ });
+ this.widgets.add("priority", this.priority);
+ this.widgets.add("back", this.back);
+ }
+
+ private void savePriority() {
+ OptionalInt priority = this.priority.getIntValue();
+ if (priority.isPresent()) {
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("priority", priority.getAsInt()));
+ }
+ }
+
+ public void drawBG(MatrixStack matrixStack, int offsetX, int offsetY, int mouseX, int mouseY, float partialTicks) {
+ super.drawBG(matrixStack, offsetX, offsetY, mouseX, mouseY, partialTicks);
+ this.priority.render(matrixStack, mouseX, mouseY, partialTicks);
+ }
+}
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..05329834d
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiFluidDualInterface.java
@@ -0,0 +1,49 @@
+package com.glodblock.github.client;
+
+import appeng.client.gui.implementations.UpgradeableScreen;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.client.gui.widgets.TabButton;
+import appeng.container.SlotSemantic;
+import appeng.core.Api;
+import appeng.core.localization.GuiText;
+import appeng.core.sync.network.NetworkHandler;
+import appeng.core.sync.packets.SwitchGuisPacket;
+import appeng.fluids.client.gui.widgets.FluidSlotWidget;
+import appeng.fluids.client.gui.widgets.FluidTankWidget;
+import appeng.fluids.util.IAEFluidTank;
+import com.glodblock.github.client.container.ContainerFCPriority;
+import com.glodblock.github.client.container.ContainerFluidDualInterface;
+import com.glodblock.github.client.container.ContainerItemDualInterface;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.ItemRenderer;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.text.ITextComponent;
+
+public class GuiFluidDualInterface extends UpgradeableScreen {
+
+ private final TabButton switchInterface;
+ private final TabButton priorityBtn;
+
+ public GuiFluidDualInterface(ContainerFluidDualInterface container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) {
+ super(container, playerInventory, title, style);
+ IAEFluidTank configFluids = this.container.getFluidConfigInventory();
+ for(int i = 0; i < 6; ++i) {
+ this.addSlot(new FluidSlotWidget(configFluids, i), SlotSemantic.CONFIG);
+ }
+ IAEFluidTank fluidTank = this.container.getTanks();
+ for(int i = 0; i < 6; ++i) {
+ this.widgets.add("tank" + (i + 1), new FluidTankWidget(fluidTank, i));
+ }
+ ItemRenderer itemRender = Minecraft.getInstance().getItemRenderer();
+ ItemStack iconItem = Api.instance().definitions().blocks().iface().maybeStack(1).orElse(ItemStack.EMPTY);
+ switchInterface = new TabButton(iconItem, iconItem.getDisplayName(), itemRender, btn ->
+ NetworkHandler.instance().sendToServer(new SwitchGuisPacket(ContainerItemDualInterface.TYPE)));
+ this.widgets.add("switchInterface", switchInterface);
+ ItemStack iconWrench = Api.instance().definitions().items().certusQuartzWrench().maybeStack(1).orElse(ItemStack.EMPTY);
+ this.priorityBtn = new TabButton(iconWrench, GuiText.Priority.text(), itemRender, btn ->
+ NetworkHandler.instance().sendToServer(new SwitchGuisPacket(ContainerFCPriority.TYPE)));
+ this.widgets.add("priorityBtn", priorityBtn);
+ }
+
+}
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..5d65b296b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiFluidPacketDecoder.java
@@ -0,0 +1,15 @@
+package com.glodblock.github.client;
+
+import appeng.client.gui.AEBaseScreen;
+import appeng.client.gui.style.ScreenStyle;
+import com.glodblock.github.client.container.ContainerFluidPacketDecoder;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.util.text.ITextComponent;
+
+public class GuiFluidPacketDecoder extends AEBaseScreen {
+
+ public GuiFluidPacketDecoder(ContainerFluidPacketDecoder container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) {
+ super(container, playerInventory, title, style);
+ }
+
+}
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..1db6c743e
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiFluidPatternTerminal.java
@@ -0,0 +1,166 @@
+package com.glodblock.github.client;
+
+import appeng.api.config.ActionItems;
+import appeng.client.gui.me.common.StackSizeRenderer;
+import appeng.client.gui.me.items.ItemTerminalScreen;
+import appeng.client.gui.style.Blitter;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.client.gui.widgets.ActionButton;
+import appeng.client.gui.widgets.TabButton;
+import appeng.container.SlotSemantic;
+import appeng.container.slot.FakeSlot;
+import appeng.core.localization.GuiText;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.client.button.BlitMap;
+import com.glodblock.github.client.button.FCToggleButton;
+import com.glodblock.github.client.container.ContainerFluidPatternTerminal;
+import com.glodblock.github.client.slot.SlotSingleItem;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.network.NetworkManager;
+import com.glodblock.github.network.packets.CPacketFluidCraftBtns;
+import com.glodblock.github.util.Ae2ReflectClient;
+import com.glodblock.github.util.FluidRenderUtils;
+import com.mojang.blaze3d.matrix.MatrixStack;
+import net.minecraft.block.Blocks;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.text.ITextComponent;
+
+import javax.annotation.Nonnull;
+
+public class GuiFluidPatternTerminal extends ItemTerminalScreen {
+ private static final String MODES_TEXTURE = "guis/pattern_modes.png";
+ private static final Blitter CRAFTING_MODE_BG = Blitter.texture(MODES_TEXTURE).src(0, 0, 126, 68);
+ private static final Blitter PROCESSING_MODE_BG = Blitter.texture(MODES_TEXTURE).src(0, 70, 126, 68);
+ private static final String SUBSTITUTION_DISABLE = "0";
+ private static final String SUBSTITUTION_ENABLE = "1";
+ private static final String CRAFTMODE_CRAFTING = "1";
+ private static final String CRAFTMODE_PROCESSING = "0";
+ private final StackSizeRenderer stackSizeRenderer = Ae2ReflectClient.getGuiStyle(this).getStackSizeRenderer();
+ private final TabButton tabCraftButton;
+ private final TabButton tabProcessButton;
+ private final ActionButton substitutionsEnabledBtn;
+ private final ActionButton substitutionsDisabledBtn;
+ private final FCToggleButton combineBtn;
+ private final FCToggleButton fluidBtn;
+
+ public GuiFluidPatternTerminal(ContainerFluidPatternTerminal container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) {
+ super(container, playerInventory, title, style);
+ this.tabCraftButton = new TabButton(
+ new ItemStack(Blocks.CRAFTING_TABLE), GuiText.CraftingPattern.text(), this.itemRenderer,
+ btn -> toggleCraftMode(CRAFTMODE_PROCESSING));
+ widgets.add("craftingPatternMode", this.tabCraftButton);
+
+ this.tabProcessButton = new TabButton(
+ new ItemStack(Blocks.FURNACE), GuiText.ProcessingPattern.text(), this.itemRenderer,
+ btn -> toggleCraftMode(CRAFTMODE_CRAFTING));
+ widgets.add("processingPatternMode", this.tabProcessButton);
+
+ this.substitutionsEnabledBtn = new ActionButton(
+ ActionItems.ENABLE_SUBSTITUTION, act -> toggleSubstitutions(SUBSTITUTION_DISABLE));
+ this.substitutionsEnabledBtn.setHalfSize(true);
+ widgets.add("substitutionsEnabled", this.substitutionsEnabledBtn);
+
+ this.substitutionsDisabledBtn = new ActionButton(
+ ActionItems.DISABLE_SUBSTITUTION, act -> toggleSubstitutions(SUBSTITUTION_ENABLE));
+ this.substitutionsDisabledBtn.setHalfSize(true);
+ widgets.add("substitutionsDisabled", this.substitutionsDisabledBtn);
+
+ this.combineBtn = new FCToggleButton(
+ btn -> {
+ FCToggleButton fbtn = (FCToggleButton) btn;
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("combine", fbtn.getActive() == 1));
+ }, BlitMap.NOT_COMBINE, BlitMap.COMBINE
+ );
+ this.combineBtn.setHalfSize(true);
+ widgets.add("combineBtn", this.combineBtn);
+
+ this.fluidBtn = new FCToggleButton(
+ btn -> {
+ FCToggleButton fbtn = (FCToggleButton) btn;
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("fluidFirst", fbtn.getActive() == 0));
+ }, BlitMap.FLUID_FIRST, BlitMap.ITEM_FIRST
+ );
+ this.fluidBtn.setHalfSize(true);
+ widgets.add("fluidBtn", this.fluidBtn);
+
+ ActionButton clearBtn = new ActionButton(ActionItems.CLOSE, act -> clear());
+ clearBtn.setHalfSize(true);
+ widgets.add("clearPattern", clearBtn);
+
+ ActionButton encodeBtn = new ActionButton(ActionItems.ENCODE, act -> encode());
+ widgets.add("encodePattern", encodeBtn);
+ }
+
+ @Override
+ protected void updateBeforeRender() {
+ super.updateBeforeRender();
+ if (this.container.isCraftingMode()) {
+ this.tabCraftButton.visible = true;
+ this.tabProcessButton.visible = false;
+
+ if (this.container.substitute) {
+ this.substitutionsEnabledBtn.visible = true;
+ this.substitutionsDisabledBtn.visible = false;
+ } else {
+ this.substitutionsEnabledBtn.visible = false;
+ this.substitutionsDisabledBtn.visible = true;
+ }
+ this.fluidBtn.visible = false;
+ this.combineBtn.visible = false;
+ } else {
+ this.tabCraftButton.visible = false;
+ this.tabProcessButton.visible = true;
+ this.substitutionsEnabledBtn.visible = false;
+ this.substitutionsDisabledBtn.visible = false;
+ this.fluidBtn.visible = true;
+ this.combineBtn.visible = true;
+ }
+
+ setSlotsHidden(SlotSemantic.CRAFTING_RESULT, !this.container.isCraftingMode());
+ setSlotsHidden(SlotSemantic.PROCESSING_RESULT, this.container.isCraftingMode());
+ }
+
+ private void toggleCraftMode(String mode) {
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("craft", mode.equals("1")));
+ }
+
+ private void toggleSubstitutions(String mode) {
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("substitute", mode.equals("1")));
+ }
+
+ private void encode() {
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("encode"));
+ }
+
+ private void clear() {
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("clear"));
+ }
+
+ @Override
+ protected void moveItems(MatrixStack matrices, Slot slot) {
+ if (!(slot instanceof FakeSlot && (FluidRenderUtils.renderFluidPacketIntoGuiSlot(
+ slot, slot.getStack(), stackSizeRenderer, font) || renderMEStyleSlot(slot, slot.getStack(), matrices)))) {
+ super.moveItems(matrices, slot);
+ }
+ }
+
+ private boolean renderMEStyleSlot(Slot slot, @Nonnull ItemStack stack, MatrixStack matrices) {
+ if (slot instanceof FakeSlot && !stack.isEmpty() && !(stack.getItem() instanceof ItemFluidPacket)) {
+ super.moveItems(matrices, new SlotSingleItem(slot));
+ if (stack.getCount() > 1) {
+ this.stackSizeRenderer.renderStackSize(font, AEItemStack.fromItemStack(stack).getStackSize(), false, slot.xPos, slot.yPos);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void drawBG(MatrixStack matrixStack, int offsetX, int offsetY, int mouseX, int mouseY, float partialTicks) {
+ super.drawBG(matrixStack, offsetX, offsetY, mouseX, mouseY, partialTicks);
+ Blitter modeBg = this.container.isCraftingMode() ? CRAFTING_MODE_BG : PROCESSING_MODE_BG;
+ modeBg.dest(this.guiLeft + 9, this.guiTop + this.ySize - 164).blit(matrixStack, this.getBlitOffset());
+ }
+}
\ 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..46db5b6b2
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiIngredientBuffer.java
@@ -0,0 +1,59 @@
+package com.glodblock.github.client;
+
+import appeng.client.gui.AEBaseScreen;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.fluids.util.IAEFluidTank;
+import com.glodblock.github.client.container.ContainerIngredientBuffer;
+import com.glodblock.github.handler.ButtonMouseHandler;
+import com.glodblock.github.handler.TankMouseHandler;
+import com.glodblock.github.util.FluidRenderUtils;
+import com.glodblock.github.util.MouseRegionManager;
+import com.mojang.blaze3d.matrix.MatrixStack;
+import com.mojang.blaze3d.platform.GlStateManager;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.PlayerContainer;
+import net.minecraft.util.text.ITextComponent;
+
+public class GuiIngredientBuffer extends AEBaseScreen {
+
+ 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 MouseRegionManager mouseRegions = new MouseRegionManager(this);
+
+ public GuiIngredientBuffer(ContainerIngredientBuffer container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) {
+ super(container, playerInventory, title, style);
+ for (int i = 0; i < 4; i++) {
+ mouseRegions.addRegion(TANK_X + TANK_X_OFF * i, TANK_Y, TANK_WIDTH, TANK_HEIGHT,
+ new TankMouseHandler(container.getTile().getFluidInventory(), i));
+ mouseRegions.addRegion(TANK_X + 10 + 22 * i, TANK_Y + TANK_HEIGHT + 2, 7, 7,
+ ButtonMouseHandler.dumpTank(container, i));
+ }
+ }
+
+ @Override
+ public boolean mouseClicked(double xCoord, double yCoord, int btn) {
+ if (mouseRegions.onClick(xCoord, yCoord, btn)) {
+ return super.mouseClicked(xCoord, yCoord, btn);
+ }
+ return true;
+ }
+
+ @Override
+ public void drawFG(MatrixStack matrixStack, int offsetX, int offsetY, int mouseX, int mouseY) {
+ GlStateManager.color4f(1F, 1F, 1F, 1F);
+ IAEFluidTank fluidInv = container.getTile().getFluidInventory();
+ assert minecraft != null;
+ minecraft.getTextureManager().bindTexture(PlayerContainer.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.getTankCapacity(i));
+ }
+ GlStateManager.color4f(1F, 1F, 1F, 1F);
+ mouseRegions.render(matrixStack, 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..c92674618
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiItemDualInterface.java
@@ -0,0 +1,93 @@
+package com.glodblock.github.client;
+
+import appeng.api.config.Settings;
+import appeng.api.config.YesNo;
+import appeng.client.gui.Icon;
+import appeng.client.gui.implementations.UpgradeableScreen;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.client.gui.widgets.ServerSettingToggleButton;
+import appeng.client.gui.widgets.SettingToggleButton;
+import appeng.client.gui.widgets.TabButton;
+import appeng.client.gui.widgets.ToggleButton;
+import appeng.core.Api;
+import appeng.core.localization.GuiText;
+import appeng.core.sync.network.NetworkHandler;
+import appeng.core.sync.packets.ConfigButtonPacket;
+import appeng.core.sync.packets.SwitchGuisPacket;
+import com.glodblock.github.client.button.BlitMap;
+import com.glodblock.github.client.button.FCToggleButton;
+import com.glodblock.github.client.container.ContainerFCPriority;
+import com.glodblock.github.client.container.ContainerFluidDualInterface;
+import com.glodblock.github.client.container.ContainerItemDualInterface;
+import com.glodblock.github.network.NetworkManager;
+import com.glodblock.github.network.packets.CPacketFluidCraftBtns;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.ItemRenderer;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.text.ITextComponent;
+
+public class GuiItemDualInterface extends UpgradeableScreen {
+
+ private final SettingToggleButton blockMode;
+ private final ToggleButton interfaceMode;
+ private final TabButton switchInterface;
+ private final TabButton priorityBtn;
+ private final FCToggleButton fluidPacketBtn;
+ private final FCToggleButton splittingBtn;
+ private final FCToggleButton blockingBtn;
+
+ public GuiItemDualInterface(ContainerItemDualInterface container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) {
+ super(container, playerInventory, title, style);
+ this.blockMode = new ServerSettingToggleButton<>(Settings.BLOCK, YesNo.NO);
+ this.addToLeftToolbar(this.blockMode);
+ this.interfaceMode = new ToggleButton(Icon.INTERFACE_TERMINAL_SHOW, Icon.INTERFACE_TERMINAL_HIDE, GuiText.InterfaceTerminal.text(), GuiText.InterfaceTerminalHint.text(), (btn) -> {
+ this.selectNextInterfaceMode();
+ });
+ this.addToLeftToolbar(this.interfaceMode);
+ ItemRenderer itemRender = Minecraft.getInstance().getItemRenderer();
+ ItemStack iconFluid = Api.instance().definitions().blocks().fluidIface().maybeStack(1).orElse(ItemStack.EMPTY);
+ this.switchInterface = new TabButton(iconFluid, iconFluid.getDisplayName(), itemRender, btn ->
+ NetworkHandler.instance().sendToServer(new SwitchGuisPacket(ContainerFluidDualInterface.TYPE)));
+ this.widgets.add("switchInterface", switchInterface);
+ ItemStack iconWrench = Api.instance().definitions().items().certusQuartzWrench().maybeStack(1).orElse(ItemStack.EMPTY);
+ this.priorityBtn = new TabButton(iconWrench, GuiText.Priority.text(), itemRender, btn ->
+ NetworkHandler.instance().sendToServer(new SwitchGuisPacket(ContainerFCPriority.TYPE)));
+ this.widgets.add("priorityBtn", priorityBtn);
+ fluidPacketBtn = new FCToggleButton(
+ btn -> {
+ FCToggleButton fbtn = (FCToggleButton) btn;
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("fluidPacket", fbtn.getActive() == 1));
+ }, BlitMap.SEND_FLUID, BlitMap.SEND_PACKET);
+ this.addToLeftToolbar(fluidPacketBtn);
+ splittingBtn = new FCToggleButton(
+ btn -> {
+ FCToggleButton fbtn = (FCToggleButton) btn;
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("allowSplitting", fbtn.getActive() == 1));
+ }, BlitMap.NOT_SPLITTING, BlitMap.SPLITTING);
+ this.addToLeftToolbar(splittingBtn);
+ blockingBtn = new FCToggleButton(
+ btn -> {
+ FCToggleButton fbtn = (FCToggleButton) btn;
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("blockModeEx", fbtn.getActive()));
+ }, BlitMap.BLOCK_ALL, BlitMap.BLOCK_ITEM, BlitMap.BLOCK_FLUID);
+ this.addToLeftToolbar(blockingBtn);
+ }
+
+ @Override
+ protected void updateBeforeRender() {
+ super.updateBeforeRender();
+ this.blockMode.set(this.container.getBlockingMode());
+ this.interfaceMode.setState(this.container.getInterfaceTerminalMode() == YesNo.YES);
+ this.fluidPacketBtn.forceActive(this.container.fluidPacket ? 1 : 0);
+ this.splittingBtn.forceActive(this.container.allowSplitting ? 1 : 0);
+ this.blockingBtn.forceActive(this.container.blockModeEx);
+ this.blockingBtn.visible = this.blockMode.getCurrentValue() == YesNo.YES;
+ }
+
+ private void selectNextInterfaceMode() {
+ boolean backwards = this.isHandlingRightClick();
+ NetworkHandler.instance().sendToServer(new ConfigButtonPacket(Settings.INTERFACE_TERMINAL, backwards));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/GuiLargeIngredientBuffer.java b/src/main/java/com/glodblock/github/client/GuiLargeIngredientBuffer.java
new file mode 100644
index 000000000..d529525e1
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/GuiLargeIngredientBuffer.java
@@ -0,0 +1,60 @@
+package com.glodblock.github.client;
+
+import appeng.client.gui.AEBaseScreen;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.fluids.util.IAEFluidTank;
+import com.glodblock.github.client.container.ContainerLargeIngredientBuffer;
+import com.glodblock.github.handler.ButtonMouseHandler;
+import com.glodblock.github.handler.TankMouseHandler;
+import com.glodblock.github.util.FluidRenderUtils;
+import com.glodblock.github.util.MouseRegionManager;
+import com.mojang.blaze3d.matrix.MatrixStack;
+import com.mojang.blaze3d.platform.GlStateManager;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.PlayerContainer;
+import net.minecraft.util.text.ITextComponent;
+
+public class GuiLargeIngredientBuffer extends AEBaseScreen {
+
+ private static final int TANK_X = 13, TANK_X_OFF = 22, TANK_Y = 18;
+ private static final int TANK_WIDTH = 16, TANK_HEIGHT = 37;
+
+ private final MouseRegionManager mouseRegions = new MouseRegionManager(this);
+
+ public GuiLargeIngredientBuffer(ContainerLargeIngredientBuffer container, PlayerInventory playerInventory, ITextComponent title, ScreenStyle style) {
+ super(container, playerInventory, title, style);
+ for (int i = 0; i < 7; i++) {
+ mouseRegions.addRegion(TANK_X + TANK_X_OFF * i, TANK_Y, TANK_WIDTH, TANK_HEIGHT,
+ new TankMouseHandler(container.getTile().getFluidInventory(), i));
+ mouseRegions.addRegion(TANK_X + 10 + 22 * i, TANK_Y + TANK_HEIGHT + 2, 7, 7,
+ ButtonMouseHandler.dumpTank(container, i));
+ }
+ }
+
+ @Override
+ public boolean mouseClicked(double xCoord, double yCoord, int btn) {
+ if (mouseRegions.onClick(xCoord, yCoord, btn)) {
+ return super.mouseClicked(xCoord, yCoord, btn);
+ }
+ return true;
+ }
+
+ @Override
+ public void drawFG(MatrixStack matrixStack, int offsetX, int offsetY, int mouseX, int mouseY) {
+ GlStateManager.color4f(1F, 1F, 1F, 1F);
+ IAEFluidTank fluidInv = container.getTile().getFluidInventory();
+ assert minecraft != null;
+ minecraft.getTextureManager().bindTexture(PlayerContainer.LOCATION_BLOCKS_TEXTURE);
+ Tessellator tess = Tessellator.getInstance();
+ BufferBuilder buf = tess.getBuffer();
+ for (int i = 0; i < 7; i++) {
+ FluidRenderUtils.renderFluidIntoGui(tess, buf, TANK_X + i * TANK_X_OFF, TANK_Y, TANK_WIDTH, TANK_HEIGHT,
+ fluidInv.getFluidInSlot(i), fluidInv.getTankCapacity(i));
+ }
+ GlStateManager.color4f(1F, 1F, 1F, 1F);
+ mouseRegions.render(matrixStack, mouseX, mouseY);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/button/BlitMap.java b/src/main/java/com/glodblock/github/client/button/BlitMap.java
new file mode 100644
index 000000000..5b17ec215
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/button/BlitMap.java
@@ -0,0 +1,52 @@
+package com.glodblock.github.client.button;
+
+import appeng.client.gui.style.Blitter;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.TranslationTextComponent;
+
+public enum BlitMap {
+
+ NOT_COMBINE(0, 0),
+ COMBINE(16, 0),
+ SEND_FLUID(2*16, 0),
+ SEND_PACKET(3*16, 0),
+ FLUID_FIRST(0, 16),
+ ITEM_FIRST(16, 16),
+ CRAFT_FLUID(2*16, 16),
+ SPLITTING(3*16, 16),
+ NOT_SPLITTING(0, 2*16),
+ BLOCK_ALL(16, 2*16),
+ BLOCK_ITEM(2*16, 2*16),
+ BLOCK_FLUID(3*16, 2*16);
+
+
+ private final int x;
+ private final int y;
+ private final int width;
+ private final int height;
+
+ BlitMap(int x, int y) {
+ this(x, y, 16, 16);
+ }
+
+ BlitMap(int x, int y, int width, int height) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+
+ public Blitter getBlitter() {
+ return Blitter.texture("gui/states.png", 64, 64).src(this.x, this.y, this.width, this.height);
+ }
+
+ public ITextComponent getDisplayName() {
+ return new TranslationTextComponent(NameConst.TT_KEY + this.toString().toLowerCase());
+ }
+
+ public ITextComponent getHint() {
+ return new TranslationTextComponent(NameConst.TT_KEY + this.toString().toLowerCase() + ".hint");
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/button/FCToggleButton.java b/src/main/java/com/glodblock/github/client/button/FCToggleButton.java
new file mode 100644
index 000000000..93a392921
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/button/FCToggleButton.java
@@ -0,0 +1,98 @@
+package com.glodblock.github.client.button;
+
+import appeng.client.gui.Icon;
+import appeng.client.gui.widgets.ITooltip;
+import com.mojang.blaze3d.matrix.MatrixStack;
+import net.minecraft.client.gui.widget.button.Button;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.StringTextComponent;
+
+import javax.annotation.Nonnull;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+public class FCToggleButton extends Button implements ITooltip {
+ private final BlitMap[] modes;
+ private final int length;
+ private int active;
+ private boolean halfSize = false;
+
+ public FCToggleButton(Button.IPressable onPress, BlitMap... modes) {
+ super(0, 0, 16, 16, StringTextComponent.EMPTY, onPress);
+ this.modes = modes;
+ this.length = modes.length;
+ }
+
+ public void next() {
+ active = (active + 1) % length;
+ }
+
+ public void forceActive(int value) {
+ active = value % length;
+ }
+
+ @Override
+ public void renderWidget(@Nonnull MatrixStack matrixStack, int mouseX, int mouseY, float partial) {
+ if (this.visible) {
+ if (this.halfSize) {
+ matrixStack.push();
+ matrixStack.translate(this.x, this.y, 0.0);
+ matrixStack.scale(0.5F, 0.5F, 1.0F);
+ Icon.TOOLBAR_BUTTON_BACKGROUND.getBlitter().dest(0, 0).blit(matrixStack, this.getBlitOffset());
+ this.getIcon().getBlitter().dest(0, 0).blit(matrixStack, this.getBlitOffset());
+ matrixStack.pop();
+ } else {
+ Icon.TOOLBAR_BUTTON_BACKGROUND.getBlitter().dest(this.x, this.y).blit(matrixStack, this.getBlitOffset());
+ this.getIcon().getBlitter().dest(this.x, this.y).blit(matrixStack, this.getBlitOffset());
+ }
+ }
+ }
+
+ public void setHalfSize(boolean value) {
+ this.halfSize = value;
+ if (value) {
+ this.width = 8;
+ this.height = 8;
+ }
+ }
+
+ @Override
+ public void onPress() {
+ this.next();
+ this.onPress.onPress(this);
+ }
+
+ private BlitMap getIcon() {
+ return this.modes[active];
+ }
+
+ public int getActive() {
+ return this.active;
+ }
+
+ @Nonnull
+ public List getTooltipMessage() {
+ return Arrays.asList(this.getIcon().getDisplayName(), this.getIcon().getHint());
+ }
+
+ public int getTooltipAreaX() {
+ return this.x;
+ }
+
+ public int getTooltipAreaY() {
+ return this.y;
+ }
+
+ public int getTooltipAreaWidth() {
+ return this.width;
+ }
+
+ public int getTooltipAreaHeight() {
+ return this.height;
+ }
+
+ public boolean isTooltipAreaVisible() {
+ return this.visible;
+ }
+}
diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFCPriority.java b/src/main/java/com/glodblock/github/client/container/ContainerFCPriority.java
new file mode 100644
index 000000000..cbbd38f5d
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerFCPriority.java
@@ -0,0 +1,69 @@
+package com.glodblock.github.client.container;
+
+import appeng.api.config.SecurityPermissions;
+import appeng.container.AEBaseContainer;
+import appeng.container.guisync.GuiSync;
+import appeng.container.implementations.ContainerTypeBuilder;
+import appeng.helpers.IPriorityHost;
+import com.glodblock.github.interfaces.ConfigData;
+import com.glodblock.github.util.ConfigSet;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.ContainerType;
+
+public class ContainerFCPriority extends AEBaseContainer implements ConfigData {
+ public static final ContainerType TYPE = ContainerTypeBuilder.create(ContainerFCPriority::new, IPriorityHost.class)
+ .requirePermission(SecurityPermissions.BUILD)
+ .withInitialData(
+ (host, buffer) -> buffer.writeVarInt(host.getPriority()),
+ (host, container, buffer) -> container.priorityValue = buffer.readVarInt())
+ .build("fc_priority");
+ private final IPriorityHost priHost;
+ private final ConfigSet exConfig = new ConfigSet();
+ @GuiSync(2)
+ public int priorityValue;
+
+ public ContainerFCPriority(int id, PlayerInventory ip, IPriorityHost te) {
+ super(TYPE, id, ip, te);
+ this.priHost = te;
+ this.priorityValue = te.getPriority();
+ this.exConfig.addConfig("priority", v -> {
+ this.priorityValue = (int) v;
+ this.priHost.setPriority((int) v);
+ }, () -> this.priorityValue);
+ }
+
+ public void setPriority(int newValue) {
+ if (newValue != this.priorityValue) {
+ if (this.isServer()) {
+ this.priHost.setPriority(newValue);
+ this.priorityValue = newValue;
+ }
+ }
+ }
+
+ public void detectAndSendChanges() {
+ super.detectAndSendChanges();
+ this.verifyPermissions(SecurityPermissions.BUILD, false);
+ if (this.isServer()) {
+ this.priorityValue = this.priHost.getPriority();
+ }
+ }
+
+ public int getPriorityValue() {
+ return this.priorityValue;
+ }
+
+ public IPriorityHost getPriorityHost() {
+ return this.priHost;
+ }
+
+ @Override
+ public void set(String id, Object value) {
+ this.exConfig.setConfig(id, value);
+ }
+
+ @Override
+ public Object get(String id) {
+ return this.exConfig.getConfig(id);
+ }
+}
diff --git a/src/main/java/com/glodblock/github/client/container/ContainerFluidDualInterface.java b/src/main/java/com/glodblock/github/client/container/ContainerFluidDualInterface.java
new file mode 100644
index 000000000..754cdb605
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidDualInterface.java
@@ -0,0 +1,82 @@
+package com.glodblock.github.client.container;
+
+import appeng.api.config.SecurityPermissions;
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.api.util.IConfigManager;
+import appeng.container.implementations.ContainerTypeBuilder;
+import appeng.fluids.container.FluidConfigurableContainer;
+import appeng.fluids.helper.DualityFluidInterface;
+import appeng.fluids.helper.FluidSyncHelper;
+import appeng.fluids.helper.IFluidInterfaceHost;
+import appeng.fluids.util.IAEFluidTank;
+import appeng.util.Platform;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraft.inventory.container.IContainerListener;
+
+import java.util.Collections;
+import java.util.Map;
+
+public class ContainerFluidDualInterface extends FluidConfigurableContainer {
+
+ private final DualityFluidInterface myDuality;
+ private final FluidSyncHelper tankSync;
+ public static final ContainerType TYPE = ContainerTypeBuilder
+ .create(ContainerFluidDualInterface::new, IFluidInterfaceHost.class)
+ .build("dual_fluid_interface");
+
+ public ContainerFluidDualInterface(int id, PlayerInventory ip, IFluidInterfaceHost te) {
+ super(TYPE, id, ip, te.getDualityFluidInterface().getHost());
+ this.myDuality = te.getDualityFluidInterface();
+ this.tankSync = new FluidSyncHelper(this.myDuality.getTanks(), 6);
+ }
+
+ protected void setupConfig() {
+ // NO-OP
+ }
+
+ protected void loadSettingsFromHost(IConfigManager cm) {
+ // NO-OP
+ }
+
+ public IAEFluidTank getTanks() {
+ return this.myDuality.getTanks();
+ }
+
+ public IAEFluidTank getFluidConfigInventory() {
+ return this.myDuality.getConfig();
+ }
+
+ @Override
+ public void detectAndSendChanges() {
+ this.verifyPermissions(SecurityPermissions.BUILD, false);
+ super.detectAndSendChanges();
+ if (Platform.isServer()) {
+ this.tankSync.sendDiff(this.listeners);
+ }
+ standardDetectAndSendChanges();
+ }
+
+ public void addListener(IContainerListener listener) {
+ super.addListener(listener);
+ this.tankSync.sendFull(Collections.singleton(listener));
+ }
+
+ public void receiveFluidSlots(Map fluids) {
+ super.receiveFluidSlots(fluids);
+ this.tankSync.readPacket(fluids);
+ }
+
+ protected boolean supportCapacity() {
+ return false;
+ }
+
+ public int availableUpgrades() {
+ return 0;
+ }
+
+ public boolean hasToolbox() {
+ return false;
+ }
+
+}
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..966f9b71f
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPacketDecoder.java
@@ -0,0 +1,23 @@
+package com.glodblock.github.client.container;
+
+import appeng.container.AEBaseContainer;
+import appeng.container.SlotSemantic;
+import appeng.container.implementations.ContainerTypeBuilder;
+import appeng.container.slot.AppEngSlot;
+import com.glodblock.github.common.tile.TileFluidPacketDecoder;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.ContainerType;
+
+public class ContainerFluidPacketDecoder extends AEBaseContainer {
+
+ public static final ContainerType TYPE = ContainerTypeBuilder
+ .create(ContainerFluidPacketDecoder::new, TileFluidPacketDecoder.class)
+ .build("fluid_packet_decoder");
+
+ public ContainerFluidPacketDecoder(int id, PlayerInventory ip, TileFluidPacketDecoder tile) {
+ super(TYPE, id, ip, tile);
+ addSlot(new AppEngSlot(tile.getInventory(), 0), SlotSemantic.STORAGE);
+ createPlayerInventorySlots(ip);
+ }
+
+}
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..de48ae667
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerFluidPatternTerminal.java
@@ -0,0 +1,558 @@
+package com.glodblock.github.client.container;
+
+import appeng.api.config.Actionable;
+import appeng.api.crafting.ICraftingHelper;
+import appeng.api.definitions.IDefinitions;
+import appeng.api.storage.IMEMonitor;
+import appeng.api.storage.ITerminalHost;
+import appeng.api.storage.channels.IItemStorageChannel;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.api.storage.data.IItemList;
+import appeng.container.ContainerNull;
+import appeng.container.SlotSemantic;
+import appeng.container.guisync.GuiSync;
+import appeng.container.implementations.ContainerTypeBuilder;
+import appeng.container.me.items.ItemTerminalContainer;
+import appeng.container.slot.*;
+import appeng.core.Api;
+import appeng.core.sync.packets.PatternSlotPacket;
+import appeng.helpers.IContainerCraftingPacket;
+import appeng.helpers.InventoryAction;
+import appeng.items.storage.ViewCellItem;
+import appeng.me.helpers.MachineSource;
+import appeng.util.InventoryAdaptor;
+import appeng.util.Platform;
+import appeng.util.inv.AdaptorItemHandler;
+import appeng.util.inv.WrapperCursorItemHandler;
+import appeng.util.item.AEItemStack;
+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.ConfigData;
+import com.glodblock.github.interfaces.PatternConsumer;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.util.ConfigSet;
+import com.glodblock.github.util.FCUtil;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.entity.player.ServerPlayerEntity;
+import net.minecraft.inventory.CraftResultInventory;
+import net.minecraft.inventory.CraftingInventory;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraft.inventory.container.CraftingResultSlot;
+import net.minecraft.inventory.container.IContainerListener;
+import net.minecraft.inventory.container.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.crafting.ICraftingRecipe;
+import net.minecraft.item.crafting.IRecipe;
+import net.minecraft.item.crafting.IRecipeType;
+import net.minecraft.world.World;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
+import net.minecraftforge.items.IItemHandler;
+import net.minecraftforge.items.ItemHandlerHelper;
+import net.minecraftforge.items.wrapper.PlayerInvWrapper;
+
+import javax.annotation.Nonnull;
+
+public class ContainerFluidPatternTerminal extends ItemTerminalContainer implements IOptionalSlotHost, IContainerCraftingPacket, PatternConsumer, ConfigData {
+
+ private final PartFluidPatternTerminal patternTerminal;
+ private final IItemHandler craftingGridInv;
+ private final FakeCraftingMatrixSlot[] craftingGridSlots = new FakeCraftingMatrixSlot[9];
+ private final OptionalFakeSlot[] processingOutputSlots = new OptionalFakeSlot[3];
+ private final PatternTermSlot craftOutputSlot;
+ private final RestrictedInputSlot blankPatternSlot;
+ private final RestrictedInputSlot encodedPatternSlot;
+ private final ICraftingHelper craftingHelper;
+ private final ConfigSet config = new ConfigSet();
+ private ICraftingRecipe currentRecipe;
+ private boolean currentRecipeCraftingMode;
+ public static ContainerType TYPE = ContainerTypeBuilder
+ .create(ContainerFluidPatternTerminal::new, ITerminalHost.class)
+ .build("fluid_pattern_terminal");
+ @GuiSync(97)
+ public boolean craftingMode;
+ @GuiSync(96)
+ public boolean substitute;
+ @GuiSync(105)
+ public boolean combine = false;
+ @GuiSync(106)
+ public boolean fluidFirst = false;
+
+ public ContainerFluidPatternTerminal(int id, PlayerInventory ip, ITerminalHost monitorable) {
+ super(TYPE, id, ip, monitorable, false);
+ this.craftingHelper = Api.INSTANCE.crafting();
+ this.craftingMode = true;
+ this.substitute = false;
+ this.patternTerminal = (PartFluidPatternTerminal) monitorable;
+ IItemHandler patternInv = this.patternTerminal.getInventoryByName("pattern");
+ IItemHandler output = this.patternTerminal.getInventoryByName("output");
+ this.craftingGridInv = this.patternTerminal.getInventoryByName("crafting");
+ int i;
+ for(i = 0; i < 9; ++i) {
+ this.addSlot(this.craftingGridSlots[i] = new FakeCraftingMatrixSlot(this.craftingGridInv, i), SlotSemantic.CRAFTING_GRID);
+ }
+
+ this.addSlot(this.craftOutputSlot = new PatternTermSlot(ip.player, this.getActionSource(), this.powerSource, monitorable, this.craftingGridInv, patternInv, this, 2, this), SlotSemantic.CRAFTING_RESULT);
+ this.craftOutputSlot.setIcon(null);
+
+ for(i = 0; i < 3; ++i) {
+ this.addSlot(this.processingOutputSlots[i] = new PatternOutputsSlot(output, this, i, 1), SlotSemantic.PROCESSING_RESULT);
+ this.processingOutputSlots[i].setRenderDisabled(false);
+ this.processingOutputSlots[i].setIcon(null);
+ }
+
+ this.addSlot(this.blankPatternSlot = new RestrictedInputSlot(RestrictedInputSlot.PlacableItemType.BLANK_PATTERN, patternInv, 0), SlotSemantic.BLANK_PATTERN);
+ this.addSlot(this.encodedPatternSlot = new RestrictedInputSlot(RestrictedInputSlot.PlacableItemType.ENCODED_PATTERN, patternInv, 1), SlotSemantic.ENCODED_PATTERN);
+ this.encodedPatternSlot.setStackLimit(1);
+ this.createPlayerInventorySlots(ip);
+ this.config
+ .addConfig("combine", v -> {
+ this.combine = (boolean) v;
+ this.patternTerminal.setCombineMode((boolean) v);
+ }, () -> this.combine)
+ .addConfig("fluidFirst", v -> {
+ this.fluidFirst = (boolean) v;
+ this.patternTerminal.setFluidPlaceMode((boolean) v);
+ }, () -> this.fluidFirst)
+ .addConfig("craft", v -> {
+ this.craftingMode = (boolean) v;
+ this.patternTerminal.setCraftingRecipe((boolean) v);
+ }, () -> this.craftingMode)
+ .addConfig("substitute", v -> {
+ this.substitute = (boolean) v;
+ this.patternTerminal.setSubstitution((boolean) v);
+ }, () -> this.substitute)
+ .addConfig("encode", v -> this.encode(),
+ () -> {throw new IllegalArgumentException("Doesn't support operation!");})
+ .addConfig("clear", v -> this.clear(),
+ () -> {throw new IllegalArgumentException("Doesn't support operation!");});
+
+ }
+
+ @Override
+ public void putStackInSlot(int slotID, @Nonnull ItemStack stack) {
+ super.putStackInSlot(slotID, stack);
+ this.getAndUpdateOutput();
+ }
+
+ private ItemStack getAndUpdateOutput() {
+ World world = this.getPlayerInventory().player.world;
+ CraftingInventory ic = new CraftingInventory(this, 3, 3);
+
+ for(int x = 0; x < ic.getSizeInventory(); ++x) {
+ ic.setInventorySlotContents(x, this.craftingGridInv.getStackInSlot(x));
+ }
+
+ if (this.currentRecipe == null || !this.currentRecipe.matches(ic, world)) {
+ this.currentRecipe = world.getRecipeManager().getRecipe(IRecipeType.CRAFTING, ic, world).orElse(null);
+ this.currentRecipeCraftingMode = this.craftingMode;
+ }
+
+ ItemStack is;
+ if (this.currentRecipe == null) {
+ is = ItemStack.EMPTY;
+ } else {
+ is = this.currentRecipe.getCraftingResult(ic);
+ }
+
+ this.craftOutputSlot.setDisplayedCraftingOutput(is);
+ return is;
+ }
+
+ public void encode() {
+ if (checkHasFluidPattern()) {
+ this.encodeFluidPattern();
+ } else {
+ this.encodeItemPattern();
+ }
+ }
+
+ public void encodeItemPattern() {
+ ItemStack output = this.encodedPatternSlot.getStack();
+ ItemStack[] in = this.getInputs();
+ ItemStack[] out = this.getOutputs();
+ if (in != null && out != null && (!this.isCraftingMode() || this.currentRecipe != null)) {
+ if (output.isEmpty() || this.craftingHelper.isEncodedPattern(output)) {
+ if (output.isEmpty()) {
+ output = this.blankPatternSlot.getStack();
+ if (output.isEmpty() || !isPattern(output)) {
+ return;
+ }
+ output.setCount(output.getCount() - 1);
+ if (output.getCount() == 0) {
+ this.blankPatternSlot.putStack(ItemStack.EMPTY);
+ }
+ output = null;
+ } else if (output.getItem() instanceof ItemFluidEncodedPattern) {
+ output = null;
+ }
+ if (this.isCraftingMode()) {
+ output = this.craftingHelper.encodeCraftingPattern(output, this.currentRecipe, in, out[0], this.isSubstitute());
+ } else {
+ output = this.craftingHelper.encodeProcessingPattern(output, in, out);
+ }
+ this.encodedPatternSlot.putStack(output);
+ }
+ }
+ }
+
+ private void encodeFluidPattern() {
+ ItemStack output = this.encodedPatternSlot.getStack();
+ ItemStack[] in = this.getInputs();
+ ItemStack[] out = this.getOutputs();
+ if (in != null && out != null && (!this.isCraftingMode() || this.currentRecipe != null)) {
+ if (output.isEmpty() || this.craftingHelper.isEncodedPattern(output)) {
+ if (output.isEmpty()) {
+ output = this.blankPatternSlot.getStack();
+ if (output.isEmpty() || !isPattern(output)) {
+ return;
+ }
+ output.setCount(output.getCount() - 1);
+ if (output.getCount() == 0) {
+ this.blankPatternSlot.putStack(ItemStack.EMPTY);
+ }
+ }
+ output = FCItems.DENSE_ENCODED_PATTERN.encodeStack(in, out);
+ this.encodedPatternSlot.putStack(output);
+ }
+ }
+ }
+
+ private static boolean isPattern(final ItemStack output) {
+ if (output.isEmpty()) {
+ return false;
+ }
+ if (output.getItem() instanceof ItemFluidEncodedPattern) {
+ return true;
+ }
+ final IDefinitions defs = Api.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.craftingGridSlots) {
+ 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.processingOutputSlots) {
+ 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 ItemStack[] getInputs() {
+ ItemStack[] input = new ItemStack[9];
+ boolean hasValue = false;
+ for(int x = 0; x < this.craftingGridSlots.length; ++x) {
+ input[x] = this.craftingGridSlots[x].getStack();
+ if (!input[x].isEmpty()) {
+ hasValue = true;
+ }
+ }
+ return hasValue ? input : null;
+ }
+
+ private ItemStack[] getOutputs() {
+ if (this.isCraftingMode()) {
+ ItemStack out = this.getAndUpdateOutput();
+ if (!out.isEmpty() && out.getCount() > 0) {
+ return new ItemStack[] {out};
+ }
+ } else {
+ boolean hasValue = false;
+ ItemStack[] list = new ItemStack[3];
+ for(int i = 0; i < this.processingOutputSlots.length; ++i) {
+ ItemStack out = this.processingOutputSlots[i].getStack();
+ list[i] = out;
+ if (!out.isEmpty()) {
+ hasValue = true;
+ }
+ }
+ if (hasValue) {
+ return list;
+ }
+ }
+ return null;
+ }
+
+ public boolean isSlotEnabled(int idx) {
+ if (idx == 1) {
+ return this.isServer() ? !this.patternTerminal.isCraftingRecipe() : !this.isCraftingMode();
+ } else if (idx == 2) {
+ return this.isServer() ? this.patternTerminal.isCraftingRecipe() : this.isCraftingMode();
+ } else {
+ return false;
+ }
+ }
+
+ public void craftOrGetItem(PatternSlotPacket packetPatternSlot) {
+ if (packetPatternSlot.slotItem != null && this.monitor != null) {
+ IAEItemStack out = packetPatternSlot.slotItem.copy();
+ InventoryAdaptor inv = new AdaptorItemHandler(new WrapperCursorItemHandler(this.getPlayerInventory().player.inventory));
+ InventoryAdaptor playerInv = InventoryAdaptor.getAdaptor(this.getPlayerInventory().player);
+ if (packetPatternSlot.shift) {
+ inv = playerInv;
+ }
+
+ if (!inv.simulateAdd(out.createItemStack()).isEmpty()) {
+ return;
+ }
+
+ IAEItemStack extracted = Platform.poweredExtraction(this.powerSource, this.monitor, out, this.getActionSource());
+ PlayerEntity p = this.getPlayerInventory().player;
+ if (extracted != null) {
+ inv.addItems(extracted.createItemStack());
+ if (p instanceof ServerPlayerEntity) {
+ this.updateHeld((ServerPlayerEntity)p);
+ }
+
+ this.detectAndSendChanges();
+ return;
+ }
+
+ CraftingInventory ic = new CraftingInventory(new ContainerNull(), 3, 3);
+ CraftingInventory real = new CraftingInventory(new ContainerNull(), 3, 3);
+
+ for(int x = 0; x < 9; ++x) {
+ ic.setInventorySlotContents(x, packetPatternSlot.pattern[x] == null ? ItemStack.EMPTY : packetPatternSlot.pattern[x].createItemStack());
+ }
+
+ IRecipe r = p.world.getRecipeManager().getRecipe(IRecipeType.CRAFTING, ic, p.world).orElse(null);
+ if (r == null) {
+ return;
+ }
+
+ IMEMonitor storage = this.patternTerminal.getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class));
+ IItemList all = storage.getStorageList();
+ ItemStack is = r.getCraftingResult(ic);
+
+ for(int x = 0; x < ic.getSizeInventory(); ++x) {
+ if (!ic.getStackInSlot(x).isEmpty()) {
+ ItemStack pulled = Platform.extractItemsByRecipe(this.powerSource, this.getActionSource(), storage, p.world, r, is, ic, ic.getStackInSlot(x), x, all, Actionable.MODULATE, ViewCellItem.createFilter(this.getViewCells()));
+ real.setInventorySlotContents(x, pulled);
+ }
+ }
+
+ IRecipe rr = p.world.getRecipeManager().getRecipe(IRecipeType.CRAFTING, real, p.world).orElse(null);
+ if (rr == r && Platform.itemComparisons().isSameItem(rr.getCraftingResult(real), is)) {
+ CraftResultInventory craftingResult = new CraftResultInventory();
+ craftingResult.setRecipeUsed(rr);
+ CraftingResultSlot sc = new CraftingResultSlot(p, real, craftingResult, 0, 0, 0);
+ sc.onTake(p, is);
+
+ for(int x = 0; x < real.getSizeInventory(); ++x) {
+ ItemStack failed = playerInv.addItems(real.getStackInSlot(x));
+ if (!failed.isEmpty()) {
+ p.dropItem(failed, false);
+ }
+ }
+
+ inv.addItems(is);
+ if (p instanceof ServerPlayerEntity) {
+ this.updateHeld((ServerPlayerEntity)p);
+ }
+
+ this.detectAndSendChanges();
+ } else {
+ for(int x = 0; x < real.getSizeInventory(); ++x) {
+ ItemStack failed = real.getStackInSlot(x);
+ if (!failed.isEmpty()) {
+ this.monitor.injectItems(AEItemStack.fromItemStack(failed), Actionable.MODULATE, new MachineSource(this.patternTerminal));
+ }
+ }
+ }
+ }
+ }
+
+ public boolean isCraftingMode() {
+ return this.craftingMode;
+ }
+
+ @Override
+ public void acceptPattern(Int2ObjectMap inputs, ItemStack[] outputs, boolean combine) {
+ this.patternTerminal.onChangeCrafting(inputs, outputs, combine);
+ }
+
+ @Override
+ public void doAction(ServerPlayerEntity player, InventoryAction action, int slotId, long id) {
+ if (this.isCraftingMode()) {
+ super.doAction(player, action, slotId, id);
+ return;
+ }
+ if (slotId < 0 || slotId >= this.inventorySlots.size()) {
+ super.doAction(player, action, slotId, id);
+ return;
+ }
+ Slot slot = getSlot(slotId);
+ ItemStack stack = player.inventory.getItemStack();
+ if ((slot instanceof FakeCraftingMatrixSlot || slot instanceof PatternOutputsSlot) && !stack.isEmpty()
+ && stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null).isPresent() && !FCUtil.getFluidFromItem(stack).isEmpty()) {
+ FluidStack fluid = FluidStack.EMPTY;
+ switch (action) {
+ case PICKUP_OR_SET_DOWN:
+ fluid = FCUtil.getFluidFromItem(stack);
+ slot.putStack(ItemFluidPacket.newStack(fluid));
+ break;
+ case SPLIT_OR_PLACE_SINGLE:
+ fluid = FCUtil.getFluidFromItem(ItemHandlerHelper.copyStackWithSize(stack, 1));
+ FluidStack origin = ItemFluidPacket.getFluidStack(slot.getStack());
+ if (!fluid.isEmpty() && fluid.equals(origin)) {
+ fluid.grow(origin.getAmount());
+ if (fluid.getAmount() <= 0)
+ fluid = FluidStack.EMPTY;
+ }
+ slot.putStack(ItemFluidPacket.newStack(fluid));
+ break;
+ }
+ if (fluid.isEmpty()) {
+ super.doAction(player, action, slotId, id);
+ return;
+ }
+ return;
+ }
+ if (action == InventoryAction.SPLIT_OR_PLACE_SINGLE) {
+ if (stack.isEmpty() && !slot.getStack().isEmpty() && slot.getStack().getItem() instanceof ItemFluidPacket) {
+ FluidStack fluid = ItemFluidPacket.getFluidStack(slot.getStack());
+ if (!fluid.isEmpty() && fluid.getAmount() - 1000 >= 1) {
+ fluid.shrink(1000);
+ slot.putStack(ItemFluidPacket.newStack(fluid));
+ }
+ }
+ }
+ super.doAction(player, action, slotId, id);
+ }
+
+ @Override
+ public void detectAndSendChanges() {
+ super.detectAndSendChanges();
+ if (Platform.isServer()) {
+ if (this.isCraftingMode() != this.patternTerminal.isCraftingRecipe()) {
+ this.setCraftingMode(this.patternTerminal.isCraftingRecipe());
+ }
+ this.substitute = this.patternTerminal.isSubstitution();
+ this.combine = patternTerminal.getCombineMode();
+ this.fluidFirst = patternTerminal.getFluidPlaceMode();
+ }
+ }
+
+ @Override
+ public void onServerDataSync() {
+ super.onServerDataSync();
+ if (this.currentRecipeCraftingMode != this.craftingMode) {
+ this.getAndUpdateOutput();
+ }
+ }
+
+ @Override
+ public void onSlotChange(Slot s) {
+ if (s == this.encodedPatternSlot && isServer()) {
+ for (final IContainerListener listener : this.listeners) {
+ for (final Slot slot : this.inventorySlots) {
+ if (slot instanceof OptionalFakeSlot || slot instanceof FakeCraftingMatrixSlot) {
+ listener.sendSlotContents(this, slot.slotNumber, slot.getStack());
+ }
+ }
+ if (listener instanceof ServerPlayerEntity) {
+ ((ServerPlayerEntity) listener).isChangingQuantityOnly = false;
+ }
+ }
+ this.detectAndSendChanges();
+ }
+
+ if (s == this.craftOutputSlot && isClient()) {
+ this.getAndUpdateOutput();
+ }
+ }
+
+ public void clear() {
+ for (final Slot s : this.craftingGridSlots) {
+ s.putStack(ItemStack.EMPTY);
+ }
+
+ for (final Slot s : this.processingOutputSlots) {
+ s.putStack(ItemStack.EMPTY);
+ }
+
+ this.detectAndSendChanges();
+ this.getAndUpdateOutput();
+ }
+
+ @Override
+ public IItemHandler getInventoryByName(final String name) {
+ if (name.equals("player")) {
+ return new PlayerInvWrapper(this.getPlayerInventory());
+ }
+ return this.patternTerminal.getInventoryByName(name);
+ }
+
+ @Override
+ public boolean useRealItems() {
+ return false;
+ }
+
+ private boolean isSubstitute() {
+ return this.substitute;
+ }
+
+ private void setCraftingMode(boolean craftingMode) {
+ this.craftingMode = craftingMode;
+ }
+
+ public void setSubstitute(boolean substitute) {
+ this.substitute = substitute;
+ }
+
+ public FakeCraftingMatrixSlot[] getCraftingGridSlots() {
+ return this.craftingGridSlots;
+ }
+
+ public OptionalFakeSlot[] getProcessingOutputSlots() {
+ return this.processingOutputSlots;
+ }
+
+ public PatternTermSlot getCraftOutputSlot() {
+ return this.craftOutputSlot;
+ }
+
+ public PartFluidPatternTerminal getPart() {
+ return this.patternTerminal;
+ }
+
+ @Override
+ public void set(String id, Object value) {
+ this.config.setConfig(id, value);
+ }
+
+ @Override
+ public Object get(String id) {
+ return this.config.getConfig(id);
+ }
+}
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..ab128809b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerIngredientBuffer.java
@@ -0,0 +1,46 @@
+package com.glodblock.github.client.container;
+
+import appeng.container.AEBaseContainer;
+import appeng.container.SlotSemantic;
+import appeng.container.implementations.ContainerTypeBuilder;
+import appeng.container.slot.AppEngSlot;
+import com.glodblock.github.common.block.BlockIngredientBuffer;
+import com.glodblock.github.interfaces.TankDumpable;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraftforge.items.IItemHandler;
+
+public class ContainerIngredientBuffer extends AEBaseContainer implements TankDumpable {
+
+ private final BlockIngredientBuffer.TileIngredientBuffer tile;
+ public static final ContainerType TYPE = ContainerTypeBuilder
+ .create(ContainerIngredientBuffer::new, BlockIngredientBuffer.TileIngredientBuffer.class)
+ .build("ingredient_buffer");
+
+ public ContainerIngredientBuffer(int id, PlayerInventory ip, BlockIngredientBuffer.TileIngredientBuffer tile) {
+ super(TYPE, id, ip, tile);
+ this.tile = tile;
+ IItemHandler inv = tile.getInternalInventory();
+ for (int i = 0; i < 9; i++) {
+ addSlot(new AppEngSlot(inv, i), SlotSemantic.STORAGE);
+ }
+ createPlayerInventorySlots(ip);
+ }
+
+ public BlockIngredientBuffer.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/ContainerItemDualInterface.java b/src/main/java/com/glodblock/github/client/container/ContainerItemDualInterface.java
new file mode 100644
index 000000000..87ebc5558
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerItemDualInterface.java
@@ -0,0 +1,121 @@
+package com.glodblock.github.client.container;
+
+import appeng.api.config.SecurityPermissions;
+import appeng.api.config.Settings;
+import appeng.api.config.YesNo;
+import appeng.api.util.IConfigManager;
+import appeng.container.SlotSemantic;
+import appeng.container.guisync.GuiSync;
+import appeng.container.implementations.ContainerTypeBuilder;
+import appeng.container.implementations.UpgradeableContainer;
+import appeng.container.slot.AppEngSlot;
+import appeng.container.slot.FakeSlot;
+import appeng.container.slot.RestrictedInputSlot;
+import appeng.helpers.DualityInterface;
+import appeng.helpers.IInterfaceHost;
+import appeng.util.Platform;
+import com.glodblock.github.interfaces.ConfigData;
+import com.glodblock.github.coreutil.ExtendedInterface;
+import com.glodblock.github.util.ConfigSet;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.ContainerType;
+
+public class ContainerItemDualInterface extends UpgradeableContainer implements ConfigData {
+
+ @GuiSync(3)
+ public YesNo bMode;
+ @GuiSync(4)
+ public YesNo iTermMode;
+ @GuiSync(95)
+ public boolean fluidPacket = false;
+ @GuiSync(96)
+ public boolean allowSplitting = true;
+ @GuiSync(97)
+ public int blockModeEx = 0;
+ private final ConfigSet exConfig = new ConfigSet();
+ private final DualityInterface dualityInterfaceCopy;
+ public static final ContainerType TYPE = ContainerTypeBuilder
+ .create(ContainerItemDualInterface::new, IInterfaceHost.class)
+ .build("dual_item_interface");
+
+ public ContainerItemDualInterface(int id, PlayerInventory ip, IInterfaceHost te) {
+ super(TYPE, id, ip, te.getInterfaceDuality().getHost());
+ this.bMode = YesNo.NO;
+ this.iTermMode = YesNo.YES;
+ this.dualityInterfaceCopy = te.getInterfaceDuality();
+ int x;
+ for (x = 0; x < 9; ++x) {
+ this.addSlot(new RestrictedInputSlot(RestrictedInputSlot.PlacableItemType.ENCODED_PATTERN, this.dualityInterfaceCopy.getPatterns(), x), SlotSemantic.ENCODED_PATTERN);
+ }
+ for (x = 0; x < 9; ++x) {
+ this.addSlot(new FakeSlot(this.dualityInterfaceCopy.getConfig(), x), SlotSemantic.CONFIG);
+ }
+ for (x = 0; x < 9; ++x) {
+ this.addSlot(new AppEngSlot(this.dualityInterfaceCopy.getStorage(), x), SlotSemantic.STORAGE);
+ }
+ this.exConfig
+ .addConfig("fluidPacket", v -> {
+ this.fluidPacket = (boolean) v;
+ ((ExtendedInterface) dualityInterfaceCopy).setFluidPacketMode((boolean) v);
+ }, () -> this.fluidPacket)
+ .addConfig("allowSplitting", v -> {
+ this.allowSplitting = (boolean) v;
+ ((ExtendedInterface) dualityInterfaceCopy).setSplittingMode((boolean) v);
+ }, () -> this.allowSplitting)
+ .addConfig("blockModeEx", v -> {
+ this.blockModeEx = (int) v;
+ ((ExtendedInterface) dualityInterfaceCopy).setExtendedBlockMode((int) v);
+ }, () -> this.blockModeEx);
+ }
+
+ protected void setupConfig() {
+ this.setupUpgrades();
+ }
+
+ public int availableUpgrades() {
+ return 1;
+ }
+
+ @Override
+ public void detectAndSendChanges() {
+ this.verifyPermissions(SecurityPermissions.BUILD, false);
+ super.detectAndSendChanges();
+ if (Platform.isServer()) {
+ fluidPacket = ((ExtendedInterface) dualityInterfaceCopy).getFluidPacketMode();
+ allowSplitting = ((ExtendedInterface) dualityInterfaceCopy).getSplittingMode();
+ blockModeEx = ((ExtendedInterface) dualityInterfaceCopy).getExtendedBlockMode();
+ }
+ }
+
+ @Override
+ protected void loadSettingsFromHost(IConfigManager cm) {
+ this.setBlockingMode((YesNo)cm.getSetting(Settings.BLOCK));
+ this.setInterfaceTerminalMode((YesNo)cm.getSetting(Settings.INTERFACE_TERMINAL));
+ }
+
+ public YesNo getBlockingMode() {
+ return this.bMode;
+ }
+
+ private void setBlockingMode(YesNo bMode) {
+ this.bMode = bMode;
+ }
+
+ public YesNo getInterfaceTerminalMode() {
+ return this.iTermMode;
+ }
+
+ private void setInterfaceTerminalMode(YesNo iTermMode) {
+ this.iTermMode = iTermMode;
+ }
+
+ @Override
+ public void set(String id, Object value) {
+ this.exConfig.setConfig(id, value);
+ }
+
+ @Override
+ public Object get(String id) {
+ return this.exConfig.getConfig(id);
+ }
+}
diff --git a/src/main/java/com/glodblock/github/client/container/ContainerLargeIngredientBuffer.java b/src/main/java/com/glodblock/github/client/container/ContainerLargeIngredientBuffer.java
new file mode 100644
index 000000000..b4e59a0cc
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/container/ContainerLargeIngredientBuffer.java
@@ -0,0 +1,46 @@
+package com.glodblock.github.client.container;
+
+import appeng.container.AEBaseContainer;
+import appeng.container.SlotSemantic;
+import appeng.container.implementations.ContainerTypeBuilder;
+import appeng.container.slot.AppEngSlot;
+import com.glodblock.github.common.block.BlockLargeIngredientBuffer;
+import com.glodblock.github.interfaces.TankDumpable;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraftforge.items.IItemHandler;
+
+public class ContainerLargeIngredientBuffer extends AEBaseContainer implements TankDumpable {
+
+ private final BlockLargeIngredientBuffer.TileLargeIngredientBuffer tile;
+ public static final ContainerType TYPE = ContainerTypeBuilder
+ .create(ContainerLargeIngredientBuffer::new, BlockLargeIngredientBuffer.TileLargeIngredientBuffer.class)
+ .build("large_ingredient_buffer");
+
+ public ContainerLargeIngredientBuffer(int id, PlayerInventory ip, BlockLargeIngredientBuffer.TileLargeIngredientBuffer tile) {
+ super(TYPE, id, ip, tile);
+ this.tile = tile;
+ IItemHandler inv = tile.getInternalInventory();
+ for (int i = 0; i < 27; i++) {
+ addSlot(new AppEngSlot(inv, i), SlotSemantic.STORAGE);
+ }
+ createPlayerInventorySlots(ip);
+ }
+
+ public BlockLargeIngredientBuffer.TileLargeIngredientBuffer 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);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/client/model/FluidEncodedPatternModel.java b/src/main/java/com/glodblock/github/client/model/FluidEncodedPatternModel.java
new file mode 100644
index 000000000..d1c3dd5f7
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/model/FluidEncodedPatternModel.java
@@ -0,0 +1,66 @@
+package com.glodblock.github.client.model;
+
+import appeng.items.misc.EncodedPatternItem;
+import com.glodblock.github.util.Ae2ReflectClient;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonObject;
+import com.mojang.datafixers.util.Pair;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.renderer.color.IItemColor;
+import net.minecraft.client.renderer.model.*;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.item.ItemStack;
+import net.minecraft.resources.IResourceManager;
+import net.minecraft.util.JSONUtils;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.client.model.IModelConfiguration;
+import net.minecraftforge.client.model.IModelLoader;
+import net.minecraftforge.client.model.geometry.IModelGeometry;
+
+import javax.annotation.Nonnull;
+import java.util.Collection;
+import java.util.Set;
+import java.util.function.Function;
+
+public class FluidEncodedPatternModel implements IModelGeometry {
+ private final ResourceLocation baseModel;
+
+ public FluidEncodedPatternModel(ResourceLocation baseModel) {
+ this.baseModel = baseModel;
+ }
+
+ public static final IItemColor PATTERN_ITEM_COLOR_HANDLER = (stack, tintIndex) -> {
+ EncodedPatternItem iep = (EncodedPatternItem) stack.getItem();
+ ItemStack output = iep.getOutput(stack);
+ if (!output.isEmpty() && Screen.hasShiftDown()) {
+ return Minecraft.getInstance().getItemColors().getColor(output, tintIndex);
+ }
+ return 0xFFFFFF;
+ };
+
+ public Collection getTextures(IModelConfiguration owner, Function modelGetter, Set> missingTextureErrors) {
+ return modelGetter.apply(this.baseModel).getTextures(modelGetter, missingTextureErrors);
+ }
+
+ public IBakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function spriteGetter, IModelTransform modelTransform, ItemOverrideList overrides, ResourceLocation modelLocation) {
+ IBakedModel baseModel = bakery.getBakedModel(this.baseModel, modelTransform, spriteGetter);
+ return Ae2ReflectClient.bakeEncodedPatternModel(baseModel);
+ }
+
+ public static class Loader implements IModelLoader {
+
+ @Override
+ public void onResourceManagerReload(@Nonnull IResourceManager iResourceManager) {
+ // NO-OP
+ }
+
+ @Nonnull
+ @Override
+ public FluidEncodedPatternModel read(@Nonnull JsonDeserializationContext jsonDeserializationContext, @Nonnull JsonObject jsonObject) {
+ jsonObject.remove("loader");
+ ResourceLocation baseModel = new ResourceLocation(JSONUtils.getString(jsonObject, "baseModel"));
+ return new FluidEncodedPatternModel(baseModel);
+ }
+ }
+}
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..5d24bb962
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/model/FluidPacketModel.java
@@ -0,0 +1,183 @@
+package com.glodblock.github.client.model;
+
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonObject;
+import com.mojang.datafixers.util.Pair;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import net.minecraft.client.renderer.model.*;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.fluid.Fluid;
+import net.minecraft.fluid.Fluids;
+import net.minecraft.item.ItemStack;
+import net.minecraft.resources.IResourceManager;
+import net.minecraft.util.Direction;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.math.vector.TransformationMatrix;
+import net.minecraftforge.client.ForgeHooksClient;
+import net.minecraftforge.client.model.*;
+import net.minecraftforge.client.model.geometry.IModelGeometry;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.registries.ForgeRegistries;
+import net.minecraftforge.resource.IResourceType;
+import net.minecraftforge.resource.VanillaResourceType;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import static net.minecraftforge.fluids.FluidAttributes.BUCKET_VOLUME;
+
+// Adapted from https://github.com/CoFH/ThermalInnovation/blob/1.16.5/src/main/java/cofh/thermal/innovation/client/model/FluidReservoirItemModel.java
+public class FluidPacketModel implements IModelGeometry {
+
+ // minimal Z offset to prevent depth-fighting
+ private static final float NORTH_Z_FLUID = 7.498F / 16F;
+ private static final float SOUTH_Z_FLUID = 8.502F / 16F;
+
+ @Nonnull
+ private final FluidStack fluidStack;
+ private final boolean isDisplay;
+
+ public FluidPacketModel(@Nonnull FluidStack fluidStack, boolean display) {
+ this.fluidStack = fluidStack;
+ this.isDisplay = display;
+ }
+
+ public FluidPacketModel set(@Nonnull FluidStack fluidStack, boolean display) {
+ return new FluidPacketModel(fluidStack, display);
+ }
+
+ @Override
+ public IBakedModel bake(IModelConfiguration owner, ModelBakery bakery, Function spriteGetter, IModelTransform modelTransform, ItemOverrideList overrides, ResourceLocation modelLocation) {
+
+ RenderMaterial particleLocation = owner.isTexturePresent("particle") ? owner.resolveTexture("particle") : null;
+ RenderMaterial fluidMaskLocation = owner.resolveTexture("layer0");
+ RenderMaterial background = owner.resolveTexture("layer1");
+
+ IModelTransform transformsFromModel = owner.getCombinedTransform();
+ Fluid fluid = fluidStack.getFluid();
+
+ TextureAtlasSprite fluidSprite;
+ if (fluidStack.isEmpty()) {
+ fluidSprite = spriteGetter.apply(ForgeHooksClient.getBlockMaterial(Fluids.WATER.getAttributes().getStillTexture()));
+ } else {
+ fluidSprite = spriteGetter.apply(ForgeHooksClient.getBlockMaterial(fluid.getAttributes().getStillTexture()));
+ }
+ ImmutableMap transformMap = PerspectiveMapWrapper.getTransforms(new ModelTransformComposition(transformsFromModel, modelTransform));
+
+ TextureAtlasSprite particleSprite = particleLocation != null ? spriteGetter.apply(particleLocation) : null;
+ if (particleSprite == null) particleSprite = fluidSprite;
+ if (particleSprite == null) particleSprite = spriteGetter.apply(background);
+
+ TransformationMatrix transform = modelTransform.getRotation();
+ ItemMultiLayerBakedModel.Builder builder = ItemMultiLayerBakedModel.builder(owner, particleSprite, new ContainedFluidOverrideHandler(bakery, owner, this), transformMap);
+
+ if (!isDisplay) {
+ builder.addQuads(ItemLayerModel.getLayerRenderType(false), ItemLayerModel.getQuadsForSprite(0, spriteGetter.apply(background), transform));
+ }
+
+ if (fluidSprite != null) {
+ TextureAtlasSprite templateSprite = spriteGetter.apply(fluidMaskLocation);
+ int luminosity = fluid.getAttributes().getLuminosity(fluidStack);
+ int color = fluid.getAttributes().getColor(fluidStack);
+ if (isDisplay) {
+ builder.addQuads(ItemLayerModel.getLayerRenderType(luminosity > 0), ItemTextureQuadConverter.genQuad(transform, 0F, 0F, 16F, 16F, NORTH_Z_FLUID, fluidSprite, Direction.NORTH, color, 2, luminosity));
+ builder.addQuads(ItemLayerModel.getLayerRenderType(luminosity > 0), ItemTextureQuadConverter.genQuad(transform, 0F, 0F, 16F, 16F, SOUTH_Z_FLUID, fluidSprite, Direction.SOUTH, color, 2, luminosity));
+ } else if (templateSprite != null) {
+ builder.addQuads(ItemLayerModel.getLayerRenderType(luminosity > 0), ItemTextureQuadConverter.convertTexture(transform, templateSprite, fluidSprite, NORTH_Z_FLUID, Direction.NORTH, color, 2, luminosity));
+ builder.addQuads(ItemLayerModel.getLayerRenderType(luminosity > 0), ItemTextureQuadConverter.convertTexture(transform, templateSprite, fluidSprite, SOUTH_Z_FLUID, Direction.SOUTH, color, 2, luminosity));
+ }
+ }
+ builder.setParticle(particleSprite);
+ return builder.build();
+ }
+
+ @Override
+ public Collection getTextures(IModelConfiguration owner, Function modelGetter, Set> missingTextureErrors) {
+ Set texs = Sets.newHashSet();
+ if (owner.isTexturePresent("particle")) {
+ texs.add(owner.resolveTexture("particle"));
+ }
+ if (owner.isTexturePresent("fluid_mask")) {
+ texs.add(owner.resolveTexture("fluid_mask"));
+ }
+ if (owner.isTexturePresent("background")) {
+ texs.add(owner.resolveTexture("background"));
+ }
+ return texs;
+ }
+
+ public static class Loader implements IModelLoader {
+
+ @Override
+ public IResourceType getResourceType() {
+ return VanillaResourceType.MODELS;
+ }
+
+ @Override
+ public void onResourceManagerReload(@Nonnull IResourceManager resourceManager) {
+ // NO-OP
+ }
+
+ @Override
+ public void onResourceManagerReload(@Nonnull IResourceManager resourceManager, @Nonnull Predicate resourcePredicate) {
+ // NO-OP
+ }
+
+ @Nonnull
+ @Override
+ public FluidPacketModel read(@Nonnull JsonDeserializationContext deserializationContext, JsonObject modelContents) {
+ FluidStack stack = FluidStack.EMPTY;
+ if (modelContents.has("fluid")) {
+ ResourceLocation fluidName = new ResourceLocation(modelContents.get("fluid").getAsString());
+ Fluid fluid = ForgeRegistries.FLUIDS.getValue(fluidName);
+ if (fluid != null) {
+ stack = new FluidStack(fluid, BUCKET_VOLUME);
+ }
+ }
+ // create new model with correct liquid
+ return new FluidPacketModel(stack, false);
+ }
+
+ }
+
+ private static final class ContainedFluidOverrideHandler extends ItemOverrideList {
+
+ private final Object2ObjectMap, IBakedModel> cache = new Object2ObjectOpenHashMap<>(); // contains all the baked models since they'll never change
+ private final ModelBakery bakery;
+ private final IModelConfiguration owner;
+ private final FluidPacketModel parent;
+
+ private ContainedFluidOverrideHandler(ModelBakery bakery, IModelConfiguration owner, FluidPacketModel parent) {
+ this.bakery = bakery;
+ this.owner = owner;
+ this.parent = parent;
+ }
+
+ @Override
+ public IBakedModel getOverrideModel(@Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable ClientWorld world, @Nullable LivingEntity entity) {
+ FluidStack fluidStack = ItemFluidPacket.getFluidStack(stack);
+ boolean display = ItemFluidPacket.isDisplay(stack);
+ Pair p = new Pair<>(fluidStack.getFluid().getRegistryName(), display);
+ if (!cache.containsKey(p)) {
+ FluidPacketModel unbaked = this.parent.set(fluidStack, display);
+ IBakedModel bakedModel = unbaked.bake(owner, bakery, ModelLoader.defaultTextureGetter(), ModelRotation.X0_Y0, this, FluidCraft.resource("fluid_packet_override"));
+ cache.put(p, bakedModel);
+ return bakedModel;
+ }
+ return cache.get(p);
+ }
+
+ }
+
+}
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..acf05dbff
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/render/DropColourHandler.java
@@ -0,0 +1,61 @@
+package com.glodblock.github.client.render;
+
+import com.glodblock.github.util.FluidRenderUtils;
+import com.glodblock.github.util.HashUtil;
+import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenCustomHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.fluid.Fluid;
+import net.minecraft.inventory.container.PlayerContainer;
+import net.minecraftforge.client.event.TextureStitchEvent;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
+import net.minecraftforge.fluids.FluidStack;
+
+public class DropColourHandler {
+
+ public static final DropColourHandler INSTANCE = new DropColourHandler();
+ private final Object2IntMap colourCache = new Object2IntLinkedOpenCustomHashMap<>(HashUtil.FLUID);
+
+ @SubscribeEvent
+ public void onTextureMapStitch(TextureStitchEvent event) {
+ if (event.getMap().getTextureLocation().equals(PlayerContainer.LOCATION_BLOCKS_TEXTURE)) {
+ colourCache.clear();
+ }
+ }
+
+ public int getColour(FluidStack fluidStack) {
+ Fluid fluid = fluidStack.getFluid();
+ int colour = fluid.getAttributes().getColor(fluidStack);
+ return colour != 0xFFFFFFFF ? colour : getColour(fluid);
+ }
+
+ public int getColour(Fluid fluid) {
+ boolean isCached = colourCache.containsKey(fluid);
+ if (isCached) {
+ return colourCache.getOrDefault(fluid, 0xFFFFFFFF);
+ }
+ int colour = fluid.getAttributes().getColor();
+ if (colour == 0xFFFFFFFF) {
+ TextureAtlasSprite sprite = FluidRenderUtils.prepareRender(fluid);
+ if (sprite != null && sprite.getFrameCount() > 0) {
+ int r = 0, g = 0, b = 0, count = 0;
+ for (int row = 0; row < sprite.getHeight(); row ++)
+ for (int col = 0; col < sprite.getWidth(); col ++) {
+ int pixel = sprite.getPixelRGBA(0, row, col);
+ if (((pixel >>> 24) & 0xFF) > 127) { // is alpha above 50%?
+ r += (pixel) & 0xFF;
+ g += (pixel >>> 8) & 0xFF;
+ b += (pixel >>> 16) & 0xFF;
+ ++count;
+ }
+ }
+ if (count > 0) {
+ colour = ((r / count) << 16) | ((g / count) << 8) | (b / count);
+ }
+ }
+ }
+ colourCache.put(fluid, colour);
+ return colour;
+ }
+
+}
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..cb02a3e2c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/render/RenderIngredientBuffer.java
@@ -0,0 +1,55 @@
+package com.glodblock.github.client.render;
+
+import com.glodblock.github.common.tile.TileSimpleBuffer;
+import com.mojang.blaze3d.matrix.MatrixStack;
+import com.mojang.blaze3d.platform.GlStateManager;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.IRenderTypeBuffer;
+import net.minecraft.client.renderer.ItemRenderer;
+import net.minecraft.client.renderer.model.ItemCameraTransforms;
+import net.minecraft.client.renderer.texture.OverlayTexture;
+import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
+import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
+import net.minecraft.inventory.container.PlayerContainer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.math.vector.Vector3f;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+
+public class RenderIngredientBuffer extends TileEntityRenderer {
+
+ public RenderIngredientBuffer(TileEntityRendererDispatcher dispatcher) {
+ super(dispatcher);
+ }
+
+ @Override
+ public void render(@Nonnull TileSimpleBuffer tile, float partialTicks, @Nonnull MatrixStack matrixStackIn, @Nonnull IRenderTypeBuffer bufferIn, int combinedLightIn, int combinedOverlayIn) {
+ GlStateManager.enableBlend();
+ GlStateManager.blendFunc(
+ GlStateManager.SourceFactor.SRC_ALPHA.param,
+ GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA.param
+ );
+ GlStateManager.enableLighting();
+ matrixStackIn.push();
+ matrixStackIn.translate(0.5D, 0.25D, 0.5D);
+ renderDispatcher.textureManager.bindTexture(PlayerContainer.LOCATION_BLOCKS_TEXTURE);
+ ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer();
+ IItemHandler inv = tile.getInternalInventory();
+ for (int i = 0; i < inv.getSlots(); i++) {
+ ItemStack stack = inv.getStackInSlot(i);
+ if (!stack.isEmpty()) {
+ matrixStackIn.push();
+ matrixStackIn.rotate(Vector3f.YP.rotation((renderDispatcher.world.getGameTime() + partialTicks) / 20.0F));
+ itemRenderer.renderItem(stack, ItemCameraTransforms.TransformType.GROUND, combinedLightIn, OverlayTexture.NO_OVERLAY, matrixStackIn, bufferIn);
+ matrixStackIn.pop();
+ break;
+ }
+ }
+
+ GlStateManager.color4f(1F, 1F, 1F, 1F);
+ GlStateManager.disableLighting();
+ matrixStackIn.pop();
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/client/slot/SlotSingleItem.java b/src/main/java/com/glodblock/github/client/slot/SlotSingleItem.java
new file mode 100644
index 000000000..a8cd8cef6
--- /dev/null
+++ b/src/main/java/com/glodblock/github/client/slot/SlotSingleItem.java
@@ -0,0 +1,114 @@
+package com.glodblock.github.client.slot;
+
+import com.mojang.datafixers.util.Pair;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.inventory.container.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+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 PlayerEntity 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);
+ }
+
+ @Nullable
+ @OnlyIn(Dist.CLIENT)
+ @Override
+ public Pair getBackground() {
+ return this.delegate.getBackground();
+ }
+
+ @Override
+ @Nonnull
+ public ItemStack decrStackSize(int amount) {
+ return delegate.decrStackSize(amount);
+ }
+
+ @Override
+ public boolean canTakeStack(@Nonnull PlayerEntity playerIn) {
+ return delegate.canTakeStack(playerIn);
+ }
+
+ @OnlyIn(Dist.CLIENT)
+ @Override
+ public boolean isEnabled() {
+ return delegate.isEnabled();
+ }
+
+ @Override
+ public int getSlotIndex() {
+ return delegate.getSlotIndex();
+ }
+
+ @Override
+ public boolean isSameInventory(@Nonnull Slot other) {
+ return delegate.isSameInventory(other);
+ }
+
+ @Nonnull
+ @Override
+ public Slot setBackground(@Nonnull ResourceLocation rl1, @Nonnull ResourceLocation rl2) {
+ return delegate.setBackground(rl1, rl2);
+ }
+
+}
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..f03f2f144
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockDualInterface.java
@@ -0,0 +1,81 @@
+package com.glodblock.github.common.block;
+
+import appeng.api.util.IOrientable;
+import appeng.block.AEBaseTileBlock;
+import appeng.container.ContainerLocator;
+import appeng.container.ContainerOpener;
+import appeng.util.Platform;
+import com.glodblock.github.client.container.ContainerItemDualInterface;
+import com.glodblock.github.common.tile.TileDualInterface;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.material.Material;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.state.BooleanProperty;
+import net.minecraft.state.DirectionProperty;
+import net.minecraft.state.StateContainer;
+import net.minecraft.util.ActionResultType;
+import net.minecraft.util.Direction;
+import net.minecraft.util.Hand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.BlockRayTraceResult;
+import net.minecraft.world.World;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.EnumSet;
+
+public class BlockDualInterface extends AEBaseTileBlock {
+
+ private static final BooleanProperty OMNIDIRECTIONAL = BooleanProperty.create("omnidirectional");
+ private static final DirectionProperty FACING = DirectionProperty.create("facing", EnumSet.allOf(Direction.class));
+
+ public BlockDualInterface() {
+ super(defaultProps(Material.IRON));
+ this.setTileEntity(TileDualInterface.class, TileDualInterface::new);
+ }
+
+ @Override
+ public ActionResultType onActivated(World w, BlockPos pos, PlayerEntity p, Hand hand, @Nullable ItemStack heldItem, BlockRayTraceResult hit) {
+ if (p.isSneaking()) {
+ return ActionResultType.PASS;
+ }
+ final TileDualInterface tg = this.getTileEntity(w, pos);
+ if (tg != null) {
+ if (Platform.isServer()) {
+ ContainerOpener.openContainer(
+ ContainerItemDualInterface.TYPE,
+ p,
+ ContainerLocator.forTileEntitySide(tg, hit.getFace())
+ );
+ }
+ return ActionResultType.func_233537_a_(w.isRemote);
+ }
+ return ActionResultType.PASS;
+ }
+
+ @Override
+ protected void fillStateContainer(@Nonnull StateContainer.Builder builder) {
+ super.fillStateContainer(builder);
+ builder.add(OMNIDIRECTIONAL, FACING);
+ }
+
+ @Override
+ protected BlockState updateBlockStateFromTileEntity(BlockState currentState, TileDualInterface te) {
+ return currentState.with(OMNIDIRECTIONAL, te.isOmniDirectional()).with(FACING, te.getForward());
+ }
+
+ @Override
+ protected boolean hasCustomRotation() {
+ return true;
+ }
+
+ @Override
+ protected void customRotateBlock(final IOrientable rotatable, final Direction axis) {
+ if (rotatable instanceof TileDualInterface) {
+ ((TileDualInterface) rotatable).setSide(axis);
+ }
+ }
+
+}
\ No newline at end of file
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..dc157ee1e
--- /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(defaultProps(Material.IRON));
+ setTileEntity(TileFluidDiscretizer.class, TileFluidDiscretizer::new);
+ }
+
+}
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..cc5a40aa7
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockFluidPacketDecoder.java
@@ -0,0 +1,43 @@
+package com.glodblock.github.common.block;
+
+import appeng.block.AEBaseTileBlock;
+import appeng.container.ContainerLocator;
+import appeng.container.ContainerOpener;
+import com.glodblock.github.client.container.ContainerFluidPacketDecoder;
+import com.glodblock.github.common.tile.TileFluidPacketDecoder;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.material.Material;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.ActionResultType;
+import net.minecraft.util.Hand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.BlockRayTraceResult;
+import net.minecraft.world.World;
+
+public class BlockFluidPacketDecoder extends AEBaseTileBlock {
+
+ public BlockFluidPacketDecoder() {
+ super(defaultProps(Material.IRON));
+ setTileEntity(TileFluidPacketDecoder.class, TileFluidPacketDecoder::new);
+ }
+
+ @Override
+ public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) {
+ if (player.isSneaking()) {
+ return super.onBlockActivated(state, world, pos, player, hand, hit);
+ }
+ TileFluidPacketDecoder tile = this.getTileEntity(world, pos);
+ if (tile != null) {
+ if (!world.isRemote) {
+ ContainerOpener.openContainer(
+ ContainerFluidPacketDecoder.TYPE,
+ player,
+ ContainerLocator.forTileEntitySide(tile, hit.getFace())
+ );
+ }
+ return ActionResultType.func_233537_a_(world.isRemote);
+ }
+ return super.onBlockActivated(state, world, pos, player, hand, hit);
+ }
+
+}
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..9520edc18
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockIngredientBuffer.java
@@ -0,0 +1,64 @@
+package com.glodblock.github.common.block;
+
+import appeng.block.AEBaseTileBlock;
+import appeng.container.ContainerLocator;
+import appeng.container.ContainerOpener;
+import appeng.fluids.util.AEFluidInventory;
+import appeng.tile.inventory.AppEngInternalInventory;
+import com.glodblock.github.client.container.ContainerIngredientBuffer;
+import com.glodblock.github.common.tile.TileSimpleBuffer;
+import com.glodblock.github.loader.FCBlocks;
+import com.glodblock.github.util.FCUtil;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.material.Material;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.ActionResultType;
+import net.minecraft.util.Hand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.BlockRayTraceResult;
+import net.minecraft.world.World;
+
+public class BlockIngredientBuffer extends AEBaseTileBlock {
+
+ public BlockIngredientBuffer() {
+ super(defaultProps(Material.IRON).setOpaque((x, y, z) -> false).notSolid());
+ setTileEntity(TileIngredientBuffer.class, TileIngredientBuffer::new);
+ }
+
+ @Override
+ public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) {
+ if (player.isSneaking()) {
+ return super.onBlockActivated(state, world, pos, player, hand, hit);
+ }
+ TileIngredientBuffer tile = this.getTileEntity(world, pos);
+ if (tile != null) {
+ if (!world.isRemote) {
+ ContainerOpener.openContainer(
+ ContainerIngredientBuffer.TYPE,
+ player,
+ ContainerLocator.forTileEntitySide(tile, hit.getFace())
+ );
+ }
+ return ActionResultType.func_233537_a_(world.isRemote);
+ }
+ return super.onBlockActivated(state, world, pos, player, hand, hit);
+ }
+
+ public static class TileIngredientBuffer extends TileSimpleBuffer {
+
+ public TileIngredientBuffer() {
+ super(FCUtil.getTileType(TileIngredientBuffer.class, FCBlocks.INGREDIENT_BUFFER));
+ }
+
+ @Override
+ protected AppEngInternalInventory createItemBuffer() {
+ return new AppEngInternalInventory(this, 9);
+ }
+
+ @Override
+ protected AEFluidInventory createFluidBuffer() {
+ return new AEFluidInventory(this, 4, 16000);
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/block/BlockLargeIngredientBuffer.java b/src/main/java/com/glodblock/github/common/block/BlockLargeIngredientBuffer.java
new file mode 100644
index 000000000..bcc9a9562
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/block/BlockLargeIngredientBuffer.java
@@ -0,0 +1,64 @@
+package com.glodblock.github.common.block;
+
+import appeng.block.AEBaseTileBlock;
+import appeng.container.ContainerLocator;
+import appeng.container.ContainerOpener;
+import appeng.fluids.util.AEFluidInventory;
+import appeng.tile.inventory.AppEngInternalInventory;
+import com.glodblock.github.client.container.ContainerLargeIngredientBuffer;
+import com.glodblock.github.common.tile.TileSimpleBuffer;
+import com.glodblock.github.loader.FCBlocks;
+import com.glodblock.github.util.FCUtil;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.material.Material;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.ActionResultType;
+import net.minecraft.util.Hand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.BlockRayTraceResult;
+import net.minecraft.world.World;
+
+public class BlockLargeIngredientBuffer extends AEBaseTileBlock {
+
+ public BlockLargeIngredientBuffer() {
+ super(defaultProps(Material.IRON).setOpaque((x, y, z) -> false).notSolid());
+ setTileEntity(TileLargeIngredientBuffer.class, TileLargeIngredientBuffer::new);
+ }
+
+ @Override
+ public ActionResultType onBlockActivated(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) {
+ if (player.isSneaking()) {
+ return super.onBlockActivated(state, world, pos, player, hand, hit);
+ }
+ TileLargeIngredientBuffer tile = this.getTileEntity(world, pos);
+ if (tile != null) {
+ if (!world.isRemote) {
+ ContainerOpener.openContainer(
+ ContainerLargeIngredientBuffer.TYPE,
+ player,
+ ContainerLocator.forTileEntitySide(tile, hit.getFace())
+ );
+ }
+ return ActionResultType.func_233537_a_(world.isRemote);
+ }
+ return super.onBlockActivated(state, world, pos, player, hand, hit);
+ }
+
+ public static class TileLargeIngredientBuffer extends TileSimpleBuffer {
+
+ public TileLargeIngredientBuffer() {
+ super(FCUtil.getTileType(TileLargeIngredientBuffer.class, FCBlocks.LARGE_INGREDIENT_BUFFER));
+ }
+
+ @Override
+ protected AppEngInternalInventory createItemBuffer() {
+ return new AppEngInternalInventory(this, 27);
+ }
+
+ @Override
+ protected AEFluidInventory createFluidBuffer() {
+ return new AEFluidInventory(this, 7, 16000);
+ }
+ }
+
+}
\ No newline at end of file
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..dd9360b89
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemFluidDrop.java
@@ -0,0 +1,140 @@
+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.fluid.Fluid;
+import net.minecraft.fluid.Fluids;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemGroup;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.util.NonNullList;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.TextFormatting;
+import net.minecraft.util.text.TranslationTextComponent;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.Constants;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.registries.ForgeRegistries;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Objects;
+
+import static com.glodblock.github.loader.FCItems.defaultProps;
+
+public class ItemFluidDrop extends Item {
+
+ public ItemFluidDrop() {
+ super(defaultProps());
+ }
+
+ @Override
+ public void fillItemGroup(@Nonnull ItemGroup tab, @Nonnull NonNullList items) {
+ if (isInGroup(tab)) {
+ items.add(newStack(new FluidStack(Fluids.WATER, 1)));
+ items.add(newStack(new FluidStack(Fluids.LAVA, 1)));
+ }
+ }
+
+ @Override
+ @Nonnull
+ public ITextComponent getDisplayName(@Nonnull ItemStack stack) {
+ FluidStack fluid = getFluidStack(stack);
+ return new TranslationTextComponent(getTranslationKey(stack),
+ !fluid.isEmpty() ? fluid.getDisplayName() : "???"
+ );
+ }
+
+ @Override
+ public void addInformation(@Nonnull ItemStack stack, @Nullable World worldIn, @Nonnull List tooltip, @Nonnull ITooltipFlag flagIn) {
+ FluidStack fluid = getFluidStack(stack);
+ if (!fluid.isEmpty()) {
+ tooltip.add(new TranslationTextComponent("%s, 1 mB", fluid.getDisplayName()).mergeStyle(TextFormatting.GRAY));
+ } else {
+ tooltip.add(new TranslationTextComponent(NameConst.TT_INVALID_FLUID).mergeStyle(TextFormatting.RED));
+ }
+ }
+
+ @Nonnull
+ public static FluidStack getFluidStack(ItemStack stack) {
+ if (stack.isEmpty() || stack.getItem() != FCItems.FLUID_DROP || !stack.hasTag()) {
+ return FluidStack.EMPTY;
+ }
+ CompoundNBT tag = Objects.requireNonNull(stack.getTag());
+ if (!tag.contains("Fluid", Constants.NBT.TAG_STRING)) {
+ return FluidStack.EMPTY;
+ }
+ Fluid fluid = ForgeRegistries.FLUIDS.getValue(new ResourceLocation(tag.getString("Fluid")));
+ if (fluid == null || fluid == Fluids.EMPTY) {
+ return FluidStack.EMPTY;
+ }
+ FluidStack fluidStack = new FluidStack(fluid, stack.getCount());
+ if (tag.contains("FluidTag", Constants.NBT.TAG_COMPOUND)) {
+ fluidStack.setTag(tag.getCompound("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;
+ }
+
+ @Nonnull
+ public static ItemStack newStack(@Nonnull FluidStack fluid) {
+ if (fluid.isEmpty()) {
+ return ItemStack.EMPTY;
+ }
+ ItemStack stack = new ItemStack(FCItems.FLUID_DROP, fluid.getAmount());
+ CompoundNBT tag = new CompoundNBT();
+ tag.putString("Fluid", String.valueOf(fluid.getFluid().getRegistryName()));
+ if (fluid.hasTag()) {
+ tag.put("FluidTag", fluid.getTag());
+ }
+ stack.setTag(tag);
+ return stack;
+ }
+
+ @Nullable
+ public static IAEItemStack newAeStack(@Nonnull FluidStack fluid) {
+ if (fluid.isEmpty()) {
+ return null;
+ }
+ IAEItemStack stack = AEItemStack.fromItemStack(newStack(fluid));
+ if (stack == null) {
+ return null;
+ }
+ stack.setStackSize(fluid.getAmount());
+ 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..d1e1b7bfc
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemFluidEncodedPattern.java
@@ -0,0 +1,206 @@
+package com.glodblock.github.common.item;
+
+import appeng.api.networking.crafting.ICraftingPatternDetails;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.core.Api;
+import appeng.core.localization.GuiText;
+import appeng.items.misc.EncodedPatternItem;
+import appeng.util.Platform;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.interfaces.HasCustomModel;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.util.FluidPatternDetails;
+import com.glodblock.github.util.InvalidFCPatternHelper;
+import com.glodblock.github.util.NameConst;
+import com.google.common.base.Preconditions;
+import net.minecraft.client.util.ITooltipFlag;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.nbt.ListNBT;
+import net.minecraft.util.NonNullList;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.StringTextComponent;
+import net.minecraft.util.text.TextFormatting;
+import net.minecraft.world.World;
+import net.minecraftforge.api.distmarker.Dist;
+import net.minecraftforge.api.distmarker.OnlyIn;
+import net.minecraftforge.fluids.FluidStack;
+
+import java.util.Collection;
+import java.util.List;
+
+import static com.glodblock.github.loader.FCItems.defaultProps;
+
+public class ItemFluidEncodedPattern extends EncodedPatternItem implements HasCustomModel {
+
+ public ItemFluidEncodedPattern() {
+ super(defaultProps());
+ }
+
+ @OnlyIn(Dist.CLIENT)
+ @Override
+ public void addInformation(ItemStack stack, World world, List lines, ITooltipFlag advancedTooltips) {
+ final ICraftingPatternDetails details = Api.instance().crafting().decodePattern(stack, world);
+ if (details == null) {
+ if (!stack.hasTag()) {
+ return;
+ }
+ stack.setDisplayName(GuiText.InvalidPattern.text().deepCopy().mergeStyle(TextFormatting.RED));
+ InvalidFCPatternHelper invalid = new InvalidFCPatternHelper(stack);
+ final ITextComponent label = (GuiText.Creates.text()).deepCopy().appendString(": ");
+ final ITextComponent and = new StringTextComponent(" ").deepCopy().appendSibling(GuiText.And.text())
+ .deepCopy()
+ .appendString(" ");
+ final ITextComponent with = GuiText.With.text().deepCopy().appendString(": ");
+ boolean first = true;
+ for (final InvalidFCPatternHelper.PatternIngredient output : invalid.getOutputs()) {
+ lines.add((first ? label : and).deepCopy().appendSibling(output.getFormattedToolTip()));
+ first = false;
+ }
+
+ first = true;
+ for (final InvalidFCPatternHelper.PatternIngredient input : invalid.getInputs()) {
+ lines.add((first ? with : and).deepCopy().appendSibling(input.getFormattedToolTip()));
+ first = false;
+ }
+ return;
+ }
+
+ if (stack.hasDisplayName()) {
+ stack.removeChildTag("display");
+ }
+
+ final boolean isCrafting = details.isCraftable();
+ final boolean substitute = details.canSubstitute();
+
+ final Collection in = details.getInputs();
+ final Collection out = details.getOutputs();
+
+ final ITextComponent label = (isCrafting ? GuiText.Crafts.text() : GuiText.Creates.text()).deepCopy()
+ .appendString(": ");
+ final ITextComponent and = new StringTextComponent(" ").deepCopy().appendSibling(GuiText.And.text())
+ .appendString(" ");
+ final ITextComponent with = GuiText.With.text().deepCopy().appendString(": ");
+
+ boolean first = true;
+ for (final IAEItemStack anOut : out) {
+ if (anOut == null) {
+ continue;
+ }
+
+ lines.add((first ? label : and).deepCopy().appendString(anOut.getStackSize() + "x ")
+ .appendSibling(Platform.getItemDisplayName(anOut)));
+ first = false;
+ }
+
+ first = true;
+ for (final IAEItemStack anIn : in) {
+ if (anIn == null) {
+ continue;
+ }
+
+ lines.add((first ? with : and).deepCopy().appendString(anIn.getStackSize() + "x ")
+ .appendSibling(Platform.getItemDisplayName(anIn)));
+ first = false;
+ }
+
+ if (isCrafting) {
+ final ITextComponent substitutionLabel = GuiText.Substitute.text().deepCopy().appendString(" ");
+ final ITextComponent canSubstitute = substitute ? GuiText.Yes.text() : GuiText.No.text();
+
+ lines.add(substitutionLabel.deepCopy().appendSibling(canSubstitute));
+ }
+ }
+
+ public ICraftingPatternDetails getDetails(ItemStack stack) {
+ return FluidPatternDetails.fromPattern(stack);
+ }
+
+ @Override
+ public boolean isEncodedPattern(ItemStack itemStack) {
+ return itemStack != null && !itemStack.isEmpty() && itemStack.getItem() instanceof ItemFluidEncodedPattern && itemStack.getTag() != null && itemStack.getTag().contains("in") && itemStack.getTag().contains("out");
+ }
+
+ @Override
+ public ItemStack getOutput(ItemStack item) {
+ ICraftingPatternDetails d = getDetails(item);
+ if (d == null) {
+ return ItemStack.EMPTY;
+ } else {
+ return d.getOutputs().size() > 0 ?
+ d.getOutputs().get(0).createItemStack() :
+ ItemStack.EMPTY;
+ }
+ }
+
+ @Override
+ public ResourceLocation getCraftingRecipeId(ItemStack itemStack) {
+ return null;
+ }
+
+ @Override
+ public List getIngredients(ItemStack itemStack) {
+ ICraftingPatternDetails d = getDetails(itemStack);
+ Preconditions.checkArgument(d != null, "Invalid fluid pattern inputs!");
+ return d.getInputs();
+ }
+
+ @Override
+ public List getProducts(ItemStack itemStack) {
+ ICraftingPatternDetails d = getDetails(itemStack);
+ Preconditions.checkArgument(d != null, "Invalid fluid pattern outputs!");
+ return d.getOutputs();
+ }
+
+ @Override
+ public boolean allowsSubstitution(ItemStack itemStack) {
+ return false;
+ }
+
+ @Override
+ public ResourceLocation getCustomModelPath() {
+ return NameConst.MODEL_DENSE_ENCODED_PATTERN;
+ }
+
+ public ItemStack encodeStack(ItemStack[] inputs, ItemStack[] outputs) {
+ ItemStack pattern = new ItemStack(FCItems.DENSE_ENCODED_PATTERN);
+ ListNBT tagIn = new ListNBT();
+ ListNBT tagOut = new ListNBT();
+ CompoundNBT v = new CompoundNBT();
+ for (ItemStack stack : inputs) {
+ tagIn.add(encodeNBT(stack));
+ }
+ for (ItemStack stack : outputs) {
+ tagOut.add(encodeNBT(stack));
+ }
+ v.put("in", tagIn);
+ v.put("out", tagOut);
+ pattern.setTag(v);
+ return pattern;
+ }
+
+ private CompoundNBT encodeNBT(ItemStack stack) {
+ if (stack.isEmpty()) {
+ return new CompoundNBT();
+ } else {
+ CompoundNBT tag = new CompoundNBT();
+ if (stack.getItem() instanceof ItemFluidPacket) {
+ FluidStack fluid = ItemFluidPacket.getFluidStack(stack);
+ if (!fluid.isEmpty()) {
+ IAEItemStack drop = ItemFluidDrop.newAeStack(fluid);
+ if (drop != null) {
+ drop.writeToNBT(tag);
+ }
+ }
+ } else {
+ IAEItemStack aeStack = AEItemStack.fromItemStack(stack);
+ if (aeStack != null) {
+ aeStack.writeToNBT(tag);
+ }
+ }
+ return tag;
+ }
+ }
+
+}
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..cca028ec7
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemFluidPacket.java
@@ -0,0 +1,130 @@
+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.item.Item;
+import net.minecraft.item.ItemGroup;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.util.NonNullList;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.TextFormatting;
+import net.minecraft.util.text.TranslationTextComponent;
+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;
+
+import static com.glodblock.github.loader.FCItems.defaultProps;
+
+public class ItemFluidPacket extends Item implements HasCustomModel {
+
+ public ItemFluidPacket() {
+ super(defaultProps().maxStackSize(1));
+ }
+
+ @Override
+ public void fillItemGroup(@Nonnull ItemGroup tab, @Nonnull NonNullList items) {
+ // NO-OP
+ }
+
+ @Override
+ @Nonnull
+ public ITextComponent getDisplayName(@Nonnull ItemStack stack) {
+ FluidStack fluid = getFluidStack(stack);
+ boolean display = isDisplay(stack);
+ if (display) {
+ return !fluid.isEmpty() ? fluid.getDisplayName() : super.getDisplayName(stack);
+ }
+ return !fluid.isEmpty() ?
+ new TranslationTextComponent(NameConst.TT_FLUID_PACKET_INFO, fluid.getDisplayName(), String.format("%,d", fluid.getAmount()))
+ : super.getDisplayName(stack);
+ }
+
+ @Override
+ public void addInformation(@Nonnull ItemStack stack, @Nullable World worldIn, @Nonnull List tooltip, @Nonnull ITooltipFlag flagIn) {
+ FluidStack fluid = getFluidStack(stack);
+ boolean display = isDisplay(stack);
+ if (display) return;
+ if (!fluid.isEmpty()) {
+ tooltip.add(new TranslationTextComponent(NameConst.TT_FLUID_PACKET).mergeStyle(TextFormatting.GRAY));
+ } else {
+ tooltip.add(new TranslationTextComponent(NameConst.TT_INVALID_FLUID).mergeStyle(TextFormatting.RED));
+ }
+ }
+
+ @Nonnull
+ public static FluidStack getFluidStack(ItemStack stack) {
+ if (stack.isEmpty() || !stack.hasTag()) {
+ return FluidStack.EMPTY;
+ }
+ return FluidStack.loadFluidStackFromNBT(Objects.requireNonNull(stack.getTag()).getCompound("FluidStack"));
+ }
+
+ public static boolean isDisplay(ItemStack stack) {
+ if (stack.isEmpty() || !stack.hasTag()) {
+ return false;
+ }
+ assert stack.getTag() != null;
+ return stack.getTag().getBoolean("DisplayOnly");
+ }
+
+ @Nonnull
+ public static FluidStack getFluidStack(@Nullable IAEItemStack stack) {
+ return stack != null ? getFluidStack(stack.getDefinition()) : FluidStack.EMPTY;
+ }
+
+ @Nonnull
+ public static ItemStack newStack(@Nonnull FluidStack fluid) {
+ if (fluid.isEmpty()) {
+ return ItemStack.EMPTY;
+ }
+ ItemStack stack = new ItemStack(FCItems.FLUID_PACKET);
+ CompoundNBT tag = new CompoundNBT();
+ CompoundNBT fluidTag = new CompoundNBT();
+ fluid.writeToNBT(fluidTag);
+ tag.put("FluidStack", fluidTag);
+ stack.setTag(tag);
+ return stack;
+ }
+
+ @Nonnull
+ public static ItemStack newDisplayStack(@Nonnull FluidStack fluid) {
+ if (fluid.isEmpty()) {
+ return ItemStack.EMPTY;
+ }
+ FluidStack copy = fluid.copy();
+ copy.setAmount(1000);
+ ItemStack stack = new ItemStack(FCItems.FLUID_PACKET);
+ CompoundNBT tag = new CompoundNBT();
+ CompoundNBT fluidTag = new CompoundNBT();
+ copy.writeToNBT(fluidTag);
+ tag.put("FluidStack", fluidTag);
+ tag.putBoolean("DisplayOnly", true);
+ stack.setTag(tag);
+ return stack;
+ }
+
+ @Nullable
+ public static IAEItemStack newAeStack(@Nonnull FluidStack fluid) {
+ return AEItemStack.fromItemStack(newStack(fluid));
+ }
+
+ @Override
+ public ResourceLocation getCustomModelPath() {
+ return NameConst.MODEL_FLUID_PACKET;
+ }
+
+ public static boolean isFluidPacket(ItemStack is) {
+ return !is.isEmpty() && is.getItem() instanceof ItemFluidPacket;
+ }
+
+}
\ 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..172548205
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemPartDualInterface.java
@@ -0,0 +1,41 @@
+package com.glodblock.github.common.item;
+
+import appeng.api.parts.IPartItem;
+import appeng.core.Api;
+import com.glodblock.github.common.part.PartDualInterface;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.ItemUseContext;
+import net.minecraft.util.ActionResultType;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import static com.glodblock.github.loader.FCItems.defaultProps;
+
+public class ItemPartDualInterface extends Item implements IPartItem {
+
+ public ItemPartDualInterface() {
+ super(defaultProps());
+ }
+
+ @Nullable
+ @Override
+ public PartDualInterface createPart(ItemStack is) {
+ return new PartDualInterface(is);
+ }
+
+ @Override
+ @Nonnull
+ public ActionResultType onItemUse(@Nonnull ItemUseContext context) {
+ return Api.instance().partHelper().placeBus(
+ context.getItem(),
+ context.getPos(),
+ context.getFace(),
+ context.getPlayer(),
+ context.getHand(),
+ context.getWorld()
+ );
+ }
+
+}
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..812b44c31
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/item/ItemPartFluidPatternTerminal.java
@@ -0,0 +1,41 @@
+package com.glodblock.github.common.item;
+
+import appeng.api.parts.IPartItem;
+import appeng.core.Api;
+import com.glodblock.github.common.part.PartFluidPatternTerminal;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.ItemUseContext;
+import net.minecraft.util.ActionResultType;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import static com.glodblock.github.loader.FCItems.defaultProps;
+
+public class ItemPartFluidPatternTerminal extends Item implements IPartItem {
+
+ public ItemPartFluidPatternTerminal() {
+ super(defaultProps());
+ }
+
+ @Nullable
+ @Override
+ public PartFluidPatternTerminal createPart(ItemStack is) {
+ return new PartFluidPatternTerminal(is);
+ }
+
+ @Override
+ @Nonnull
+ public ActionResultType onItemUse(@Nonnull ItemUseContext context) {
+ return Api.instance().partHelper().placeBus(
+ context.getItem(),
+ context.getPos(),
+ context.getFace(),
+ context.getPlayer(),
+ context.getHand(),
+ context.getWorld()
+ );
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/me/DualityDualInterface.java b/src/main/java/com/glodblock/github/common/me/DualityDualInterface.java
new file mode 100644
index 000000000..fcdbc2f0c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/me/DualityDualInterface.java
@@ -0,0 +1,204 @@
+package com.glodblock.github.common.me;
+
+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.fluids.util.AEFluidInventory;
+import appeng.helpers.DualityInterface;
+import appeng.helpers.IInterfaceHost;
+import appeng.me.helpers.AENetworkProxy;
+import appeng.util.SettingsFrom;
+import appeng.util.inv.InvOperation;
+import com.google.common.collect.ImmutableSet;
+import net.minecraft.inventory.CraftingInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.util.Direction;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.common.capabilities.ICapabilityProvider;
+import net.minecraftforge.common.util.LazyOptional;
+import net.minecraftforge.fluids.capability.IFluidHandler;
+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) + fluidDuality.getInstalledUpgrades(u);
+ }
+
+ public int getPriority() {
+ return itemDuality.getPriority(); // both interfaces should always have the same priority
+ }
+
+ public void setPriority(final int newValue) {
+ itemDuality.setPriority(newValue);
+ fluidDuality.setPriority(newValue);
+ }
+
+ @Nonnull
+ @Override
+ public LazyOptional getCapability(@Nonnull Capability capability, @Nullable Direction facing) {
+ LazyOptional capInst = itemDuality.getCapability(capability, facing);
+ return capInst.isPresent() ? 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();
+ }
+
+ 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, CraftingInventory 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 CompoundNBT data) {
+ CompoundNBT itemIfaceTag = new CompoundNBT(), fluidIfaceTag = new CompoundNBT();
+ itemDuality.writeToNBT(itemIfaceTag);
+ fluidDuality.writeToNBT(fluidIfaceTag);
+ data.put("itemDuality", itemIfaceTag);
+ data.put("fluidDuality", fluidIfaceTag);
+ }
+
+ public void readFromNBT(final CompoundNBT data) {
+ itemDuality.readFromNBT(data.getCompound("itemDuality"));
+ fluidDuality.readFromNBT(data.getCompound("fluidDuality"));
+ }
+
+ public CompoundNBT downloadSettings(SettingsFrom from) {
+ CompoundNBT tag = new CompoundNBT();
+ if (from == SettingsFrom.MEMORY_CARD) {
+ final IFluidHandler fluidInv = this.fluidDuality.getFluidInventoryByName("config");
+ if (fluidInv instanceof AEFluidInventory) {
+ ((AEFluidInventory) fluidInv).writeToNBT(tag, "fluid_config");
+ }
+ }
+ return tag;
+ }
+
+ public void uploadSettings(SettingsFrom from, CompoundNBT compound) {
+ final IFluidHandler fluidInv = this.fluidDuality.getFluidInventoryByName("config");
+ if (from == SettingsFrom.MEMORY_CARD && fluidInv instanceof AEFluidInventory) {
+ AEFluidInventory target = (AEFluidInventory) fluidInv;
+ AEFluidInventory tmp = new AEFluidInventory(null, target.getSlots());
+ tmp.readFromNBT(compound, "fluid_config");
+ for(int x = 0; x < tmp.getSlots(); x++) {
+ target.setFluidInSlot(x, tmp.getFluidInSlot(x));
+ }
+ }
+ }
+
+}
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..a407b7069
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/part/PartDualInterface.java
@@ -0,0 +1,261 @@
+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.container.ContainerLocator;
+import appeng.container.ContainerOpener;
+import appeng.fluids.helper.DualityFluidInterface;
+import appeng.fluids.helper.IFluidInterfaceHost;
+import appeng.helpers.DualityInterface;
+import appeng.helpers.IInterfaceHost;
+import appeng.helpers.IPriorityHost;
+import appeng.items.parts.PartModels;
+import appeng.parts.BasicStatePart;
+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.client.container.ContainerItemDualInterface;
+import com.glodblock.github.common.me.DualityDualInterface;
+import com.glodblock.github.loader.FCItems;
+import com.google.common.collect.ImmutableSet;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.inventory.CraftingInventory;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.Direction;
+import net.minecraft.util.Hand;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.math.vector.Vector3d;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.common.util.LazyOptional;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+import java.util.EnumSet;
+import java.util.List;
+
+public class PartDualInterface extends BasicStatePart
+ implements IGridTickable, IInventoryDestination, IInterfaceHost, IAEAppEngInventory, IPriorityHost, 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);
+
+ 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 CompoundNBT data) {
+ super.readFromNBT(data);
+ duality.readFromNBT(data);
+ }
+
+ @Override
+ public void writeToNBT(final CompoundNBT 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(PlayerEntity player, Hand hand, Vector3d pos) {
+ if (Platform.isServer()) {
+ ContainerOpener.openContainer(ContainerItemDualInterface.TYPE, player, ContainerLocator.forPart(this));
+ }
+ 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 CraftingInventory 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);
+ }
+
+ @Nonnull
+ @Override
+ public LazyOptional getCapability(Capability capabilityClass) {
+ return duality.getCapability(capabilityClass, getSide().getFacing());
+ }
+
+ @Override
+ public ItemStack getItemStackRepresentation() {
+ return new ItemStack(FCItems.PART_DUAL_INTERFACE, 1);
+ }
+
+ @Override
+ public ContainerType> getContainerType() {
+ return ContainerItemDualInterface.TYPE;
+ }
+
+ @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;
+ }
+ }
+
+}
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..3caf53a89
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/part/PartFluidPatternTerminal.java
@@ -0,0 +1,175 @@
+package com.glodblock.github.common.part;
+
+import appeng.api.config.SecurityPermissions;
+import appeng.api.networking.crafting.ICraftingPatternDetails;
+import appeng.api.parts.IPartModel;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.api.storage.data.IItemList;
+import appeng.container.me.items.ItemTerminalContainer;
+import appeng.items.parts.PartModels;
+import appeng.parts.PartModel;
+import appeng.parts.reporting.PatternTerminalPart;
+import appeng.tile.inventory.AppEngInternalInventory;
+import appeng.util.Platform;
+import appeng.util.inv.InvOperation;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.container.ContainerFluidPatternTerminal;
+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.util.FCUtil;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.items.IItemHandler;
+import net.minecraftforge.items.IItemHandlerModifiable;
+
+import javax.annotation.Nonnull;
+
+public class PartFluidPatternTerminal extends PatternTerminalPart {
+
+ private boolean combine = false;
+ private boolean fluidFirst = false;
+ private final AppEngInternalInventory crafting;
+ private final AppEngInternalInventory output;
+ private final AppEngInternalInventory pattern;
+
+ @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);
+ this.crafting = (AppEngInternalInventory) getInventoryByName("crafting");
+ this.output = (AppEngInternalInventory) getInventoryByName("output");
+ this.pattern = (AppEngInternalInventory) getInventoryByName("pattern");
+ }
+
+ @Nonnull
+ @Override
+ public IPartModel getStaticModels() {
+ return this.selectModel(MODELS_OFF, MODELS_ON, MODELS_HAS_CHANNEL);
+ }
+
+ @Override
+ public void readFromNBT(CompoundNBT data) {
+ super.readFromNBT(data);
+ combine = data.getBoolean("combineMode");
+ fluidFirst = data.getBoolean("fluidFirst");
+ }
+
+ public void setCombineMode(boolean value) {
+ this.combine = value;
+ }
+
+ public boolean getCombineMode() {
+ return this.combine;
+ }
+
+ public void setFluidPlaceMode(boolean value) {
+ this.fluidFirst = value;
+ }
+
+ public boolean getFluidPlaceMode() {
+ return this.fluidFirst;
+ }
+
+ @Override
+ public void writeToNBT(CompoundNBT data) {
+ super.writeToNBT(data);
+ data.putBoolean("combineMode", combine);
+ data.putBoolean("fluidFirst", fluidFirst);
+ }
+
+ @Override
+ public ContainerType> getContainerType(PlayerEntity p) {
+ return Platform.checkPermissions(p, this, SecurityPermissions.CRAFT, false) ?
+ ContainerFluidPatternTerminal.TYPE : ItemTerminalContainer.TYPE;
+ }
+
+ @Override
+ public void onChangeInventory(IItemHandler inv, int slot, InvOperation mc, ItemStack removedStack,
+ ItemStack newStack) {
+ if (slot == 1 && inv == this.pattern) {
+ final ItemStack is = inv.getStackInSlot(1);
+ if (!is.isEmpty() && is.getItem() instanceof ItemFluidEncodedPattern) {
+ final ItemFluidEncodedPattern pattern = (ItemFluidEncodedPattern) is.getItem();
+ final ICraftingPatternDetails details = pattern.getDetails(is);
+ if(details != null) {
+ this.setCraftingRecipe(details.isCraftable());
+ this.setSubstitution(details.canSubstitute());
+ for(int x = 0; x < this.crafting.getSlots(); x ++) {
+ this.crafting.setStackInSlot(x, ItemStack.EMPTY);
+ }
+ for(int x = 0; x < this.output.getSlots(); x ++) {
+ this.output.setStackInSlot(x, ItemStack.EMPTY);
+ }
+ putPattern(details.getInputs().toArray(new IAEItemStack[0]), details.getOutputs().toArray(new IAEItemStack[0]));
+ }
+ this.getHost().markForSave();
+ return;
+ }
+ }
+ super.onChangeInventory(inv, slot, mc, removedStack, newStack);
+ }
+
+ public void onChangeCrafting(Int2ObjectMap inputs, ItemStack[] outputs, boolean combine) {
+ IItemHandler crafting = this.getInventoryByName("crafting");
+ IItemHandler output = this.getInventoryByName("output");
+ IItemList storageList = this.getInventory(FCUtil.ITEM) == null ?
+ null : this.getInventory(FCUtil.ITEM).getStorageList();
+ if (crafting instanceof AppEngInternalInventory && output instanceof AppEngInternalInventory) {
+ FCUtil.clearItemInventory((IItemHandlerModifiable) crafting);
+ FCUtil.clearItemInventory((IItemHandlerModifiable) output);
+ ItemStack[] fuzzyFind = new ItemStack[FCUtil.findMax(inputs.keySet()) + 1];
+ for (int index : inputs.keySet()) {
+ FCUtil.fuzzyTransferItems(index, inputs.get(index), fuzzyFind, storageList);
+ }
+ if (combine && !this.isCraftingRecipe()) {
+ fuzzyFind = FCUtil.compress(fuzzyFind);
+ }
+ int bound = Math.min(crafting.getSlots(), fuzzyFind.length);
+ for (int x = 0; x < bound; x++) {
+ final ItemStack item = fuzzyFind[x];
+ ((AppEngInternalInventory) crafting).setStackInSlot(x, item == null ? ItemStack.EMPTY : item);
+ }
+ bound = Math.min(output.getSlots(), outputs.length);
+ for (int x = 0; x < bound; x++) {
+ final ItemStack item = outputs[x];
+ ((AppEngInternalInventory) output).setStackInSlot(x, item == null ? ItemStack.EMPTY : item);
+ }
+ }
+ }
+
+ public void putPattern(IAEItemStack[] inputs, IAEItemStack[] outputs) {
+ for( int x = 0; x < this.getInventoryByName("crafting").getSlots() && x < inputs.length; x++ )
+ {
+ final IAEItemStack item = inputs[x];
+ if (item != null && item.getItem() instanceof ItemFluidDrop) {
+ ItemStack packet = ItemFluidPacket.newStack(ItemFluidDrop.getFluidStack(item.createItemStack()));
+ ((AppEngInternalInventory) this.getInventoryByName("crafting")).setStackInSlot(x, packet);
+ }
+ else ((AppEngInternalInventory) this.getInventoryByName("crafting")).setStackInSlot( x, item == null ? ItemStack.EMPTY : item.createItemStack() );
+ }
+
+ for( int x = 0; x < this.getInventoryByName("output").getSlots() && x < outputs.length; x++ )
+ {
+ final IAEItemStack item = outputs[x];
+ if (item != null && item.getItem() instanceof ItemFluidDrop) {
+ ItemStack packet = ItemFluidPacket.newStack(ItemFluidDrop.getFluidStack(item.createItemStack()));
+ ((AppEngInternalInventory) this.getInventoryByName("output")).setStackInSlot(x, packet);
+ }
+ else ((AppEngInternalInventory) this.getInventoryByName("output")).setStackInSlot( x, item == null ? ItemStack.EMPTY : item.createItemStack() );
+ }
+ }
+
+}
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..349eae179
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileDualInterface.java
@@ -0,0 +1,327 @@
+package com.glodblock.github.common.tile;
+
+import appeng.api.config.Actionable;
+import appeng.api.config.Upgrades;
+import appeng.api.networking.GridFlags;
+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.helpers.IPriorityHost;
+import appeng.tile.grid.AENetworkInvTileEntity;
+import appeng.util.Platform;
+import appeng.util.SettingsFrom;
+import appeng.util.inv.IInventoryDestination;
+import appeng.util.inv.InvOperation;
+import com.glodblock.github.client.container.ContainerItemDualInterface;
+import com.glodblock.github.common.me.DualityDualInterface;
+import com.glodblock.github.loader.FCBlocks;
+import com.glodblock.github.util.FCUtil;
+import com.google.common.collect.ImmutableSet;
+import net.minecraft.block.BlockState;
+import net.minecraft.inventory.CraftingInventory;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.Direction;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.common.util.LazyOptional;
+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 AENetworkInvTileEntity
+ implements IGridTickable, IInventoryDestination, IInterfaceHost, IPriorityHost, IFluidInterfaceHost {
+
+ public TileDualInterface() {
+ super(FCUtil.getTileType(TileDualInterface.class, FCBlocks.DUAL_INTERFACE));
+ getProxy().setIdlePowerUsage(4D);
+ getProxy().setFlags(GridFlags.REQUIRE_CHANNEL);
+ }
+
+ 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 Direction facing) {
+ if (Platform.isClient()) {
+ return;
+ }
+
+ Direction 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(Direction.NORTH, Direction.UP);
+ } else {
+ Direction newUp = Direction.UP;
+ if (newForward == Direction.UP || newForward == Direction.DOWN) {
+ newUp = Direction.NORTH;
+ }
+ this.setOrientation(newForward, newUp);
+ }
+
+ this.configureNodeSides();
+ this.markForUpdate();
+ this.saveChanges();
+ }
+
+ private void configureNodeSides() {
+ if (this.omniDirectional) {
+ this.getProxy().setValidSides(EnumSet.allOf(Direction.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 CompoundNBT write(final CompoundNBT data) {
+ super.write(data);
+ data.putBoolean("omniDirectional", this.omniDirectional);
+ duality.writeToNBT(data);
+ return data;
+ }
+
+ @Override
+ public void read(BlockState blockState, CompoundNBT data) {
+ super.read(blockState, data);
+ this.omniDirectional = data.getBoolean("omniDirectional");
+ duality.readFromNBT(data);
+ }
+
+ @Override
+ protected boolean readFromStream(PacketBuffer 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(PacketBuffer 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(Direction.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 CraftingInventory 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;
+ }
+
+ @Nonnull
+ @Override
+ public LazyOptional getCapability(@Nonnull Capability capability, @Nullable Direction facing) {
+ LazyOptional capInst = duality.getCapability(capability, facing);
+ return capInst.isPresent() ? capInst : super.getCapability(capability, facing);
+ }
+
+ @Override
+ public ItemStack getItemStackRepresentation() {
+ return new ItemStack(FCBlocks.DUAL_INTERFACE);
+ }
+
+ @Override
+ public ContainerType> getContainerType() {
+ return ContainerItemDualInterface.TYPE;
+ }
+
+ @Override
+ public CompoundNBT downloadSettings(SettingsFrom from) {
+ CompoundNBT pre = super.downloadSettings(from);
+ CompoundNBT tag = pre == null ? new CompoundNBT() : pre;
+ tag.put("pattern", this.duality.downloadSettings(from));
+ return tag.isEmpty() ? null : tag;
+ }
+
+ @Override
+ public void uploadSettings(SettingsFrom from, CompoundNBT compound) {
+ super.uploadSettings(from, compound);
+ if (compound.contains("pattern")) {
+ this.duality.uploadSettings(from, compound.getCompound("pattern"));
+ }
+ }
+
+}
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..c3949b740
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileFluidDiscretizer.java
@@ -0,0 +1,288 @@
+package com.glodblock.github.common.tile;
+
+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.*;
+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.cells.ICellContainer;
+import appeng.api.storage.cells.ICellInventory;
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.api.storage.data.IItemList;
+import appeng.me.GridAccessException;
+import appeng.me.cache.CraftingGridCache;
+import appeng.me.helpers.MachineSource;
+import appeng.me.storage.MEInventoryHandler;
+import appeng.tile.grid.AENetworkTileEntity;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.loader.FCBlocks;
+import com.glodblock.github.util.FCUtil;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+
+public class TileFluidDiscretizer extends AENetworkTileEntity 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;
+
+ public TileFluidDiscretizer() {
+ super(FCUtil.getTileType(TileFluidDiscretizer.class, FCBlocks.FLUID_DISCRETIZER));
+ 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 == FCUtil.ITEM) {
+ return Collections.singletonList(fluidDropInv.invHandler);
+ } else if (channel == FCUtil.FLUID) {
+ return Collections.singletonList(fluidCraftInv.invHandler);
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void saveChanges(@Nullable ICellInventory> cellInventory) {
+ if (world != null) {
+ 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();
+ }
+
+ @MENetworkEventSubscribe
+ public void onStorageUpdate(MENetworkStorageEvent 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(FCUtil.FLUID);
+ } catch (GridAccessException e) {
+ return null;
+ }
+ }
+
+ private class FluidDiscretizingInventory implements IMEInventory, IMEMonitorHandlerReceiver {
+
+ private final MEInventoryHandler invHandler = new MEInventoryHandler<>(this, getChannel());
+ @Nullable
+ private ObjectArrayList 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(fluidGrid.extractItems(fluidStack, mode, ownActionSource));
+ }
+
+ @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(fluidGrid.injectItems(fluidStack, type, ownActionSource));
+ }
+
+ @Override
+ public IItemList getAvailableItems(IItemList out) {
+ if (itemCache == null) {
+ itemCache = new ObjectArrayList<>();
+ 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 {
+ ObjectArrayList mappedChanges = new ObjectArrayList<>();
+ for (IAEFluidStack fluidStack : change) {
+ boolean isNg = false;
+ if (fluidStack.getStackSize() < 0) {
+ isNg = true;
+ fluidStack.setStackSize( - fluidStack.getStackSize() );
+ }
+ IAEItemStack itemStack = ItemFluidDrop.newAeStack(fluidStack);
+ if (itemStack != null) {
+ if (isNg) itemStack.setStackSize( - itemStack.getStackSize() );
+ 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 FCUtil.ITEM;
+ }
+
+ }
+
+ 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 FCUtil.FLUID;
+ }
+
+ }
+
+}
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..99fa7e24c
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileFluidPacketDecoder.java
@@ -0,0 +1,125 @@
+package com.glodblock.github.common.tile;
+
+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.data.IAEFluidStack;
+import appeng.fluids.util.AEFluidStack;
+import appeng.me.GridAccessException;
+import appeng.me.helpers.MachineSource;
+import appeng.tile.grid.AENetworkTileEntity;
+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 com.glodblock.github.loader.FCBlocks;
+import com.glodblock.github.util.FCUtil;
+import net.minecraft.block.BlockState;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.util.Direction;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.common.util.LazyOptional;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+import net.minecraftforge.items.IItemHandlerModifiable;
+
+import javax.annotation.Nonnull;
+
+public class TileFluidPacketDecoder extends AENetworkTileEntity implements IGridTickable, IAEAppEngInventory {
+
+ private final AppEngInternalInventory inventory = new AppEngInternalInventory(this, 1);
+ private final IActionSource ownActionSource = new MachineSource(this);
+
+ public TileFluidPacketDecoder() {
+ super(FCUtil.getTileType(TileFluidPacketDecoder.class, FCBlocks.FLUID_PACKET_DECODER));
+ getProxy().setIdlePowerUsage(1.5D);
+ getProxy().setFlags(GridFlags.REQUIRE_CHANNEL);
+ }
+
+ public IItemHandlerModifiable getInventory() {
+ return inventory;
+ }
+
+ @Override
+ public boolean canBeRotated() {
+ return false;
+ }
+
+ @Nonnull
+ @SuppressWarnings("unchecked")
+ @Override
+ public LazyOptional getCapability(@Nonnull Capability capability, Direction facing) {
+ if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
+ return LazyOptional.of(() -> (T)inventory);
+ } else {
+ return LazyOptional.empty();
+ }
+ }
+
+ @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.isEmpty()) {
+ 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(FCUtil.FLUID);
+ 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 CompoundNBT write(CompoundNBT data) {
+ super.write(data);
+ inventory.writeToNBT(data, "Inventory");
+ return data;
+ }
+
+ @Override
+ public void read(BlockState blockState, CompoundNBT data) {
+ super.read(blockState, data);
+ inventory.readFromNBT(data, "Inventory");
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/common/tile/TileSimpleBuffer.java b/src/main/java/com/glodblock/github/common/tile/TileSimpleBuffer.java
new file mode 100644
index 000000000..fbb87203e
--- /dev/null
+++ b/src/main/java/com/glodblock/github/common/tile/TileSimpleBuffer.java
@@ -0,0 +1,102 @@
+package com.glodblock.github.common.tile;
+
+import appeng.fluids.util.AEFluidInventory;
+import appeng.fluids.util.IAEFluidInventory;
+import appeng.fluids.util.IAEFluidTank;
+import appeng.tile.AEBaseInvTileEntity;
+import appeng.tile.inventory.AppEngInternalInventory;
+import appeng.util.inv.InvOperation;
+import com.glodblock.github.util.FCUtil;
+import net.minecraft.block.BlockState;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.tileentity.TileEntityType;
+import net.minecraft.util.Direction;
+import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.common.util.LazyOptional;
+import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
+import net.minecraftforge.items.CapabilityItemHandler;
+import net.minecraftforge.items.IItemHandler;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+
+public abstract class TileSimpleBuffer extends AEBaseInvTileEntity implements IAEFluidInventory {
+
+ private final AppEngInternalInventory invItems = createItemBuffer();
+ private final AEFluidInventory invFluids = createFluidBuffer();
+
+ public TileSimpleBuffer(TileEntityType> type) {
+ super(type);
+ }
+
+ abstract protected AppEngInternalInventory createItemBuffer();
+
+ abstract protected AEFluidInventory createFluidBuffer();
+
+ @Nonnull
+ @Override
+ public IItemHandler getInternalInventory() {
+ return invItems;
+ }
+
+ public IAEFluidTank getFluidInventory() {
+ return invFluids;
+ }
+
+ @Override
+ public boolean canBeRotated() {
+ return false;
+ }
+
+ @Nonnull
+ @SuppressWarnings("unchecked")
+ @Override
+ public LazyOptional getCapability(@Nonnull Capability capability, Direction facing) {
+ if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) {
+ return LazyOptional.of(() -> (T)invItems);
+ } else if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
+ return LazyOptional.of(() -> (T)invFluids);
+ }
+ return super.getCapability(capability, facing);
+ }
+
+ @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(PacketBuffer data) throws IOException {
+ super.writeToStream(data);
+ FCUtil.writeFluidInventoryToBuffer(invFluids, data);
+ }
+
+ @Override
+ protected boolean readFromStream(PacketBuffer data) throws IOException {
+ boolean changed = super.readFromStream(data);
+ changed |= FCUtil.readFluidInventoryToBuffer(invFluids, data);
+ return changed;
+ }
+
+ @Override
+ public void read(BlockState blockState, CompoundNBT data) {
+ super.read(blockState, data);
+ invFluids.readFromNBT(data, "FluidInv");
+ }
+
+ @Override
+ public CompoundNBT write(CompoundNBT data) {
+ super.write(data);
+ invFluids.writeToNBT(data, "FluidInv");
+ return data;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/coreutil/ExtendedInterface.java b/src/main/java/com/glodblock/github/coreutil/ExtendedInterface.java
new file mode 100644
index 000000000..a13ed3612
--- /dev/null
+++ b/src/main/java/com/glodblock/github/coreutil/ExtendedInterface.java
@@ -0,0 +1,29 @@
+package com.glodblock.github.coreutil;
+
+public interface ExtendedInterface {
+
+ default boolean getFluidPacketMode() {
+ return false;
+ }
+
+ default void setFluidPacketMode(boolean value) {
+
+ }
+
+ default boolean getSplittingMode() {
+ return false;
+ }
+
+ default void setSplittingMode(boolean value) {
+
+ }
+
+ default int getExtendedBlockMode() {
+ return 0;
+ }
+
+ default void setExtendedBlockMode(int value) {
+
+ }
+
+}
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..8ba35a870
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/ButtonMouseHandler.java
@@ -0,0 +1,49 @@
+package com.glodblock.github.handler;
+
+import com.glodblock.github.interfaces.TankDumpable;
+import com.glodblock.github.network.NetworkManager;
+import com.glodblock.github.network.packets.CPacketDumpTank;
+import com.glodblock.github.util.MouseRegionManager;
+import com.glodblock.github.util.NameConst;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.TranslationTextComponent;
+
+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(new TranslationTextComponent(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)) {
+ NetworkManager.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..67e9188d9
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/ClientRegistryHandler.java
@@ -0,0 +1,66 @@
+package com.glodblock.github.handler;
+
+import appeng.core.Api;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.*;
+import com.glodblock.github.client.container.*;
+import com.glodblock.github.client.model.FluidEncodedPatternModel;
+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 com.glodblock.github.loader.FCBlocks;
+import com.glodblock.github.util.Ae2ReflectClient;
+import net.minecraft.block.Block;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.RenderTypeLookup;
+import net.minecraft.client.renderer.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.eventbus.api.SubscribeEvent;
+import org.apache.commons.lang3.tuple.Pair;
+
+public class ClientRegistryHandler extends RegistryHandler {
+
+ public static final ClientRegistryHandler INSTANCE = new ClientRegistryHandler();
+
+ @Override
+ public void onInit() {
+ RenderTypeLookup.setRenderLayer(FCBlocks.INGREDIENT_BUFFER, RenderType.getCutout());
+ RenderTypeLookup.setRenderLayer(FCBlocks.LARGE_INGREDIENT_BUFFER, RenderType.getCutout());
+ Ae2ReflectClient.registerAEGui(ContainerIngredientBuffer.TYPE, GuiIngredientBuffer::new, "/screens/ingredient_buffer.json");
+ Ae2ReflectClient.registerAEGui(ContainerLargeIngredientBuffer.TYPE, GuiLargeIngredientBuffer::new, "/screens/large_ingredient_buffer.json");
+ Ae2ReflectClient.registerAEGui(ContainerItemDualInterface.TYPE, GuiItemDualInterface::new, "/screens/dual_item_interface.json");
+ Ae2ReflectClient.registerAEGui(ContainerFluidDualInterface.TYPE, GuiFluidDualInterface::new, "/screens/dual_fluid_interface.json");
+ Ae2ReflectClient.registerAEGui(ContainerFluidPatternTerminal.TYPE, GuiFluidPatternTerminal::new, "/screens/fluid_pattern_terminal.json");
+ Ae2ReflectClient.registerAEGui(ContainerFluidPacketDecoder.TYPE, GuiFluidPacketDecoder::new, "/screens/fluid_packet_decoder.json");
+ Ae2ReflectClient.registerAEGui(ContainerFCPriority.TYPE, GuiFCPriority::new, "/screens/fc_priority.json");
+ }
+
+ @SubscribeEvent
+ public void onRegisterModels(ModelRegistryEvent event) {
+ ModelLoaderRegistry.registerLoader(FluidCraft.resource("fluid_encoded_pattern"), new FluidEncodedPatternModel.Loader());
+ //ModelLoaderRegistry.registerLoader(new DenseCraftEncodedPatternModel.Loader());
+ ModelLoaderRegistry.registerLoader(FluidCraft.resource("fluid_packet"), new FluidPacketModel.Loader());
+ for (Pair entry : blocks) {
+ registerModel(entry.getLeft(), entry.getRight().asItem());
+ }
+ for (Pair entry : items) {
+ registerModel(entry.getLeft(), entry.getRight());
+ }
+
+ Api.instance().registries().partModels().registerModels(PartDualInterface.MODELS);
+ Api.instance().registries().partModels().registerModels(PartFluidPatternTerminal.MODELS);
+ //AEApi.instance().registries().partModels().registerModels(PartExtendedFluidPatternTerminal.MODELS);
+ }
+
+ private static void registerModel(String key, Item item) {
+ ModelLoader.addSpecialModel(new ModelResourceLocation(
+ item instanceof HasCustomModel ?
+ ((HasCustomModel)item).getCustomModelPath() : FluidCraft.resource(key),
+ "inventory"));
+ }
+
+}
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..18a49c4e1
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/RegistryHandler.java
@@ -0,0 +1,106 @@
+package com.glodblock.github.handler;
+
+import appeng.block.AEBaseBlockItem;
+import appeng.block.AEBaseTileBlock;
+import appeng.core.features.ActivityState;
+import appeng.core.features.BlockStackSrc;
+import appeng.tile.AEBaseTileEntity;
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.client.container.*;
+import com.glodblock.github.loader.FCItems;
+import com.glodblock.github.util.FCUtil;
+import net.minecraft.block.Block;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraft.item.Item;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.tileentity.TileEntityType;
+import net.minecraftforge.event.RegistryEvent;
+import net.minecraftforge.eventbus.api.SubscribeEvent;
+import net.minecraftforge.registries.ForgeRegistries;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RegistryHandler {
+
+ public static final RegistryHandler INSTANCE = new RegistryHandler();
+ protected final List> blocks = new ArrayList<>();
+ protected final List> items = new ArrayList<>();
+ protected final List>> tiles = new ArrayList<>();
+
+ public void block(String name, Block block) {
+ blocks.add(Pair.of(name, block));
+ if (block instanceof AEBaseTileBlock) {
+ AEBaseTileBlock> tileBlock = (AEBaseTileBlock>) block;
+ tile(name, tileBlock.getTileEntityClass(), block);
+ }
+ }
+
+ public void item(String name, Item item) {
+ items.add(Pair.of(name, item));
+ }
+
+ public void tile(String name, Class extends TileEntity> clazz, Block block) {
+ tiles.add(Pair.of(name, FCUtil.getTileType(clazz, block)));
+ }
+
+ @SubscribeEvent
+ public void onRegisterBlocks(RegistryEvent.Register event) {
+ for (Pair entry : blocks) {
+ String key = entry.getLeft();
+ Block block = entry.getRight();
+ block.setRegistryName(key);
+ event.getRegistry().register(block);
+ }
+ }
+
+ @SubscribeEvent
+ public void onRegisterItems(RegistryEvent.Register- event) {
+ for (Pair entry : blocks) {
+ event.getRegistry().register(initItem(entry.getLeft(), new AEBaseBlockItem(entry.getRight(), new Item.Properties().group(FCItems.TAB_AE2FC))));
+ }
+ for (Pair entry : items) {
+ event.getRegistry().register(initItem(entry.getLeft(), entry.getRight()));
+ }
+ }
+
+ @SubscribeEvent
+ public void onRegisterTileEntities(RegistryEvent.Register> event) {
+ for (Pair> entry : tiles) {
+ String key = entry.getLeft();
+ TileEntityType> tile = entry.getRight();
+ tile.setRegistryName(key);
+ event.getRegistry().register(tile);
+ }
+ }
+
+ @SubscribeEvent
+ public void onRegisterContainerTypes(RegistryEvent.Register> event) {
+ event.getRegistry().register(ContainerIngredientBuffer.TYPE);
+ event.getRegistry().register(ContainerLargeIngredientBuffer.TYPE);
+ event.getRegistry().register(ContainerItemDualInterface.TYPE);
+ event.getRegistry().register(ContainerFluidPatternTerminal.TYPE);
+ event.getRegistry().register(ContainerFluidDualInterface.TYPE);
+ event.getRegistry().register(ContainerFluidPacketDecoder.TYPE);
+ event.getRegistry().register(ContainerFCPriority.TYPE);
+ }
+
+ private static Item initItem(String key, Item item) {
+ item.setRegistryName(key);
+ return item;
+ }
+
+ public void onInit() {
+ for (Pair entry : blocks) {
+ Block block = ForgeRegistries.BLOCKS.getValue(FluidCraft.resource(entry.getKey()));
+ if (block instanceof AEBaseTileBlock) {
+ AEBaseTileEntity.registerTileItem(
+ ((AEBaseTileBlock>) block).getTileEntityClass(),
+ new BlockStackSrc(block, 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..42c322398
--- /dev/null
+++ b/src/main/java/com/glodblock/github/handler/TankMouseHandler.java
@@ -0,0 +1,39 @@
+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.ITextComponent;
+import net.minecraft.util.text.StringTextComponent;
+import net.minecraft.util.text.TextFormatting;
+import net.minecraft.util.text.TranslationTextComponent;
+
+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(
+ new TranslationTextComponent(fluid != null ? fluid.getFluidStack().getTranslationKey() : NameConst.TT_EMPTY),
+ new StringTextComponent(
+ TextFormatting.GRAY + String.format("%,d / %,d mB",
+ fluid != null ? fluid.getStackSize() : 0L, tank.getTankCapacity(index)))
+ );
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/builder/RecipeTransferBuilder.java b/src/main/java/com/glodblock/github/integration/builder/RecipeTransferBuilder.java
new file mode 100644
index 000000000..ae55ed36b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/builder/RecipeTransferBuilder.java
@@ -0,0 +1,138 @@
+package com.glodblock.github.integration.builder;
+
+import com.glodblock.github.common.item.ItemFluidPacket;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import mezz.jei.api.gui.IRecipeLayout;
+import mezz.jei.api.gui.ingredient.IGuiIngredient;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class RecipeTransferBuilder {
+
+ private static final int MAX_ITEMS = 16;
+
+ private final Int2ObjectArrayMap in;
+ private final int bound;
+ private final ItemStack[] out;
+ private final IRecipeLayout recipe;
+ private List itemsIn;
+ private List fluidIn;
+ private List itemOut;
+ private List fluidOut;
+ private boolean noNull = true;
+ private boolean fluidFirst = false;
+
+ public RecipeTransferBuilder(int maxInput, int maxOutput, IRecipeLayout recipe) {
+ this.in = new Int2ObjectArrayMap<>();
+ this.bound = maxInput;
+ this.out = new ItemStack[maxOutput];
+ this.recipe = recipe;
+ this.itemsIn = new ArrayList<>();
+ this.itemOut = new ArrayList<>();
+ this.fluidIn = new ArrayList<>();
+ this.fluidOut = new ArrayList<>();
+ this.split();
+ }
+
+ private void split() {
+ for (int index = 0; index < this.recipe.getItemStacks().getGuiIngredients().size(); index ++) {
+ IGuiIngredient ing = this.recipe.getItemStacks().getGuiIngredients().get(index);
+ if (ing.isInput()) {
+ List holder;
+ if (ing.getAllIngredients().size() < MAX_ITEMS - 1) {
+ holder = ing.getAllIngredients();
+ } else {
+ holder = ing.getAllIngredients().subList(0, MAX_ITEMS - 1);
+ }
+ // Put displayed item at first check
+ if (ing.getDisplayedIngredient() != null) {
+ holder.add(0, ing.getDisplayedIngredient());
+ }
+ this.itemsIn.add(holder.toArray(new ItemStack[0]));
+ } else {
+ this.itemOut.add(ing.getDisplayedIngredient());
+ }
+ }
+ for (int index = 0; index < this.recipe.getFluidStacks().getGuiIngredients().size(); index ++) {
+ IGuiIngredient ing = this.recipe.getFluidStacks().getGuiIngredients().get(index);
+ if (ing.isInput()) {
+ this.fluidIn.add(ing.getDisplayedIngredient());
+ } else {
+ this.fluidOut.add(ing.getDisplayedIngredient());
+ }
+ }
+ }
+
+ private void setItemIn(int offset) {
+ int bound = Math.min(this.bound, this.itemsIn.size() + offset);
+ for (int index = offset; index < bound; index ++) {
+ int i = index - offset;
+ if (this.itemsIn.get(i) != null && this.itemsIn.get(i).length > 0) {
+ this.in.put(index, this.itemsIn.get(i));
+ }
+ }
+ }
+
+ private void setFluidIn(int offset) {
+ int bound = Math.min(this.bound, this.fluidIn.size() + offset);
+ for (int index = offset; index < bound; index ++) {
+ int i = index - offset;
+ if (this.fluidIn.get(i) != null) {
+ this.in.put(index, new ItemStack[] {ItemFluidPacket.newStack(this.fluidIn.get(i))});
+ }
+ }
+ }
+
+ private void setOutputs() {
+ for (int index = 0; index < this.out.length; index ++) {
+ if (index < this.itemOut.size()) {
+ this.out[index] = this.itemOut.get(index);
+ } else if (index - this.itemOut.size() < this.fluidOut.size()) {
+ this.out[index] = ItemFluidPacket.newStack(this.fluidOut.get(index - this.itemOut.size()));
+ }
+ }
+ }
+
+ public RecipeTransferBuilder clearEmptySlot(boolean val) {
+ this.noNull = val;
+ return this;
+ }
+
+ public RecipeTransferBuilder putFluidFirst(boolean val) {
+ this.fluidFirst = val;
+ return this;
+ }
+
+ public RecipeTransferBuilder build() {
+ if (this.noNull) {
+ this.itemsIn = this.itemsIn.stream().filter(o -> o != null && o.length > 0).collect(Collectors.toList());
+ this.itemOut = this.itemOut.stream().filter(Objects::nonNull).collect(Collectors.toList());
+ this.fluidIn = this.fluidIn.stream().filter(Objects::nonNull).collect(Collectors.toList());
+ this.fluidOut = this.fluidOut.stream().filter(Objects::nonNull).collect(Collectors.toList());
+ }
+ if (this.fluidFirst) {
+ this.setFluidIn(0);
+ this.setItemIn(this.fluidIn.size());
+ } else {
+ this.setItemIn(0);
+ this.setFluidIn(this.itemsIn.size());
+ }
+ this.setOutputs();
+ return this;
+ }
+
+ public ItemStack[] getOutput() {
+ return this.out;
+ }
+
+ public Int2ObjectMap getInput() {
+ return this.in;
+ }
+
+}
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..12bd09271
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/jei/FCJeiPlugin.java
@@ -0,0 +1,26 @@
+package com.glodblock.github.integration.jei;
+
+import com.glodblock.github.FluidCraft;
+import com.glodblock.github.integration.jei.handlers.FluidPatternTerminalRecipeTransferHandler;
+import mezz.jei.api.IModPlugin;
+import mezz.jei.api.JeiPlugin;
+import mezz.jei.api.registration.IRecipeTransferRegistration;
+import net.minecraft.util.ResourceLocation;
+
+import javax.annotation.Nonnull;
+
+@JeiPlugin
+public class FCJeiPlugin implements IModPlugin {
+
+ @Nonnull
+ @Override
+ public ResourceLocation getPluginUid() {
+ return FluidCraft.resource("jei");
+ }
+
+ @Override
+ public void registerRecipeTransferHandlers(@Nonnull IRecipeTransferRegistration registration) {
+ registration.addUniversalRecipeTransferHandler(new FluidPatternTerminalRecipeTransferHandler());
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/integration/jei/handlers/FluidPatternTerminalRecipeTransferHandler.java b/src/main/java/com/glodblock/github/integration/jei/handlers/FluidPatternTerminalRecipeTransferHandler.java
new file mode 100644
index 000000000..2d8da22fc
--- /dev/null
+++ b/src/main/java/com/glodblock/github/integration/jei/handlers/FluidPatternTerminalRecipeTransferHandler.java
@@ -0,0 +1,50 @@
+package com.glodblock.github.integration.jei.handlers;
+
+import com.glodblock.github.client.container.ContainerFluidPatternTerminal;
+import com.glodblock.github.common.part.PartFluidPatternTerminal;
+import com.glodblock.github.integration.builder.RecipeTransferBuilder;
+import com.glodblock.github.network.NetworkManager;
+import com.glodblock.github.network.packets.CPacketFluidCraftBtns;
+import com.glodblock.github.network.packets.CPacketLoadPattern;
+import mezz.jei.api.constants.VanillaRecipeCategoryUid;
+import mezz.jei.api.gui.IRecipeLayout;
+import mezz.jei.api.recipe.transfer.IRecipeTransferError;
+import mezz.jei.api.recipe.transfer.IRecipeTransferHandler;
+import net.minecraft.entity.player.PlayerEntity;
+
+import javax.annotation.Nonnull;
+
+public class FluidPatternTerminalRecipeTransferHandler implements IRecipeTransferHandler {
+
+ @Nonnull
+ @Override
+ public Class getContainerClass() {
+ return ContainerFluidPatternTerminal.class;
+ }
+
+ @Override
+ public IRecipeTransferError transferRecipe(@Nonnull ContainerFluidPatternTerminal container, @Nonnull Object recipe, @Nonnull IRecipeLayout recipeLayout, @Nonnull PlayerEntity player, boolean maxTransfer, boolean doTransfer) {
+ if (doTransfer) {
+ boolean craftMode = container.craftingMode;
+ if (container.isCraftingMode() && !recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) {
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("craft", false));
+ craftMode = false;
+ }
+ else if (!container.isCraftingMode() && recipeLayout.getRecipeCategory().getUid().equals(VanillaRecipeCategoryUid.CRAFTING)) {
+ NetworkManager.netHandler.sendToServer(new CPacketFluidCraftBtns("craft", true));
+ craftMode = true;
+ }
+ PartFluidPatternTerminal tile = container.getPart();
+ RecipeTransferBuilder transfer = new RecipeTransferBuilder(
+ tile.getInventoryByName("crafting").getSlots(),
+ tile.getInventoryByName("output").getSlots(),
+ recipeLayout)
+ .clearEmptySlot(!craftMode)
+ .putFluidFirst(container.fluidFirst)
+ .build();
+ NetworkManager.netHandler.sendToServer(new CPacketLoadPattern(transfer.getInput(), transfer.getOutput(), container.combine));
+ }
+ return null;
+ }
+
+}
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/ConfigData.java b/src/main/java/com/glodblock/github/interfaces/ConfigData.java
new file mode 100644
index 000000000..5dda1ddd4
--- /dev/null
+++ b/src/main/java/com/glodblock/github/interfaces/ConfigData.java
@@ -0,0 +1,9 @@
+package com.glodblock.github.interfaces;
+
+public interface ConfigData {
+
+ void set(String id, Object value);
+
+ Object get(String id);
+
+}
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..89fce380a
--- /dev/null
+++ b/src/main/java/com/glodblock/github/interfaces/PatternConsumer.java
@@ -0,0 +1,10 @@
+package com.glodblock.github.interfaces;
+
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.item.ItemStack;
+
+public interface PatternConsumer {
+
+ void acceptPattern(Int2ObjectMap inputs, ItemStack[] outputs, boolean compress);
+
+}
\ 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/FluidConvertingInventoryAdaptor.java b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java
new file mode 100644
index 000000000..5878fc1ec
--- /dev/null
+++ b/src/main/java/com/glodblock/github/inventory/FluidConvertingInventoryAdaptor.java
@@ -0,0 +1,369 @@
+package com.glodblock.github.inventory;
+
+import appeng.api.config.FuzzyMode;
+import appeng.api.parts.IPart;
+import appeng.fluids.helper.DualityFluidInterface;
+import appeng.fluids.helper.IFluidInterfaceHost;
+import appeng.helpers.DualityInterface;
+import appeng.helpers.IInterfaceHost;
+import appeng.me.GridAccessException;
+import appeng.me.helpers.AENetworkProxy;
+import appeng.tile.misc.InterfaceTileEntity;
+import appeng.tile.networking.CableBusTileEntity;
+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.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.common.tile.TileDualInterface;
+import com.glodblock.github.coreutil.ExtendedInterface;
+import com.glodblock.github.util.Ae2Reflect;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.Direction;
+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.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 InventoryAdaptor wrap(ICapabilityProvider capProvider, Direction face) {
+ TileEntity cap = (TileEntity) capProvider;
+ TileEntity inter = Objects.requireNonNull(cap.getWorld()).getTileEntity(cap.getPos().add(face.getDirectionVec()));
+ DualityInterface dualInterface = getInterfaceTE(inter, face) == null ?
+ null : Objects.requireNonNull(getInterfaceTE(inter, face)).getInterfaceDuality();
+ boolean onmi = false;
+ if (inter instanceof InterfaceTileEntity) {
+ onmi = ((InterfaceTileEntity) inter).getTargets().size() > 1;
+ } else if (inter instanceof TileDualInterface) {
+ onmi = ((TileDualInterface) inter).getTargets().size() > 1;
+ }
+
+ if (dualInterface == null || !((ExtendedInterface) dualInterface).getFluidPacketMode()) {
+ return new FluidConvertingInventoryAdaptor(
+ capProvider.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, face).resolve().orElse(null),
+ capProvider.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, face).resolve().orElse(null),
+ inter,
+ onmi,
+ dualInterface);
+ }
+ return InventoryAdaptor.getAdaptor(cap, face);
+ }
+
+ @Nullable
+ private final InventoryAdaptor invItems;
+ @Nullable
+ private final IFluidHandler invFluids;
+ private final boolean onmi;
+ @Nullable
+ private final TileEntity posInterface;
+ @Nullable
+ private final DualityInterface self;
+
+ public FluidConvertingInventoryAdaptor(@Nullable IItemHandler invItems, @Nullable IFluidHandler invFluids,
+ @Nullable TileEntity pos, boolean isOnmi, @Nullable DualityInterface interSelf) {
+ this.invItems = invItems != null ? new AdaptorItemHandler(invItems) : null;
+ this.invFluids = invFluids;
+ this.posInterface = pos;
+ this.onmi = isOnmi;
+ this.self = interSelf;
+ }
+
+ @Override
+ public ItemStack addItems(@Nonnull ItemStack toBeAdded) {
+ if (toBeAdded.getItem() instanceof ItemFluidPacket || toBeAdded.getItem() instanceof ItemFluidDrop) {
+ if (onmi) {
+ FluidStack fluid;
+ if (toBeAdded.getItem() instanceof ItemFluidPacket) {
+ fluid = ItemFluidPacket.getFluidStack(toBeAdded);
+ } else {
+ fluid = ItemFluidDrop.getFluidStack(toBeAdded);
+ }
+
+ // First try to output to the same side
+ if (invFluids != null) {
+ if (!fluid.isEmpty()) {
+ int filled = invFluids.fill(fluid, IFluidHandler.FluidAction.EXECUTE);
+ if (filled > 0) {
+ fluid.shrink(filled);
+ return ItemFluidPacket.newStack(fluid);
+ }
+ }
+ }
+
+ if (!fluid.isEmpty() && posInterface != null && posInterface.getWorld() != null
+ && self != null && ((ExtendedInterface) self).getSplittingMode()) {
+ for (Direction dir : Direction.values()) {
+ TileEntity te = posInterface.getWorld().getTileEntity(posInterface.getPos().add(dir.getDirectionVec()));
+ if (te != null) {
+ IInterfaceHost interTE = getInterfaceTE(te, dir);
+ if (interTE != null && isSameGrid(interTE)) {
+ continue;
+ }
+ IFluidInterfaceHost interFTE = getFluidInterfaceTE(te, dir);
+ if (interFTE != null && isSameGrid(interFTE)) {
+ continue;
+ }
+ IFluidHandler fh = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, dir.getOpposite())
+ .resolve().orElse(null);
+ if (fh != null) {
+ int filled = fh.fill(fluid, IFluidHandler.FluidAction.SIMULATE);
+ if (filled == fluid.getAmount()) {
+ fh.fill(fluid, IFluidHandler.FluidAction.EXECUTE);
+ return ItemStack.EMPTY;
+ }
+ }
+ }
+ }
+ }
+ return ItemFluidPacket.newStack(fluid);
+ }
+ if (invFluids != null) {
+ FluidStack fluid;
+ if(toBeAdded.getItem() instanceof ItemFluidPacket)
+ fluid = ItemFluidPacket.getFluidStack(toBeAdded);
+ else
+ fluid = ItemFluidDrop.getFluidStack(toBeAdded);
+ if (!fluid.isEmpty()) {
+ int filled = invFluids.fill(fluid, IFluidHandler.FluidAction.EXECUTE);
+ if (filled > 0) {
+ fluid.shrink(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 || toBeSimulated.getItem() instanceof ItemFluidDrop) {
+ if (onmi) {
+ boolean sus = false;
+ FluidStack fluid;
+ if (toBeSimulated.getItem() instanceof ItemFluidPacket) {
+ fluid = ItemFluidPacket.getFluidStack(toBeSimulated);
+ } else {
+ fluid = ItemFluidDrop.getFluidStack(toBeSimulated);
+ }
+
+ // First try to output to the same side
+ if (invFluids != null) {
+ if (!fluid.isEmpty()) {
+ int filled = invFluids.fill(fluid, IFluidHandler.FluidAction.SIMULATE);
+ if (filled > 0) {
+ fluid.shrink(filled);
+ return ItemFluidPacket.newStack(fluid);
+ }
+ }
+ }
+
+ if (!fluid.isEmpty() && posInterface != null && posInterface.getWorld() != null && self != null) {
+ if (((ExtendedInterface) self).getSplittingMode()) {
+ for (Direction dir : Direction.values()) {
+ TileEntity te = posInterface.getWorld().getTileEntity(posInterface.getPos().add(dir.getDirectionVec()));
+ if (te != null) {
+ IInterfaceHost interTE = getInterfaceTE(te, dir);
+ if (interTE != null && isSameGrid(interTE)) {
+ continue;
+ }
+ IFluidInterfaceHost interFTE = getFluidInterfaceTE(te, dir);
+ if (interFTE != null && isSameGrid(interFTE)) {
+ continue;
+ }
+ IFluidHandler fh = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, dir.getOpposite())
+ .resolve().orElse(null);
+ if (fh != null) {
+ int filled = fh.fill(fluid, IFluidHandler.FluidAction.SIMULATE);
+ if (filled == fluid.getAmount()) {
+ sus = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ sus = true;
+ }
+ return sus ? ItemStack.EMPTY : toBeSimulated;
+ }
+ if (invFluids != null) {
+ FluidStack fluid;
+ if(toBeSimulated.getItem() instanceof ItemFluidPacket)
+ fluid = ItemFluidPacket.getFluidStack(toBeSimulated);
+ else
+ fluid = ItemFluidDrop.getFluidStack(toBeSimulated);
+ if (!fluid.isEmpty()) {
+ int filled = invFluids.fill(fluid, IFluidHandler.FluidAction.SIMULATE);
+ if (filled > 0) {
+ fluid.shrink(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() {
+ int blockMode = 0;
+ if (this.self != null) {
+ blockMode = ((ExtendedInterface) this.self).getExtendedBlockMode();
+ }
+ boolean checkFluid = blockMode != 1;
+ boolean checkItem = blockMode != 2;
+ if (invFluids != null && checkFluid) {
+ for (int i = 0; i < invFluids.getTanks(); i ++) {
+ FluidStack fluid = invFluids.getFluidInTank(i);
+ if (!fluid.isEmpty()) {
+ return true;
+ }
+ }
+ }
+ if (invItems != null && checkItem) {
+ return invItems.containsItems();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean hasSlots() {
+ return (invFluids != null && invFluids.getTanks() > 0)
+ || (invItems != null && invItems.hasSlots());
+ }
+
+ @Nullable
+ protected static IInterfaceHost getInterfaceTE(TileEntity te, Direction face) {
+ if (te instanceof IInterfaceHost) {
+ return (IInterfaceHost) te;
+ } else if (te instanceof CableBusTileEntity) {
+ IPart part = ((CableBusTileEntity) te).getPart(face.getOpposite());
+ if (part instanceof IInterfaceHost) {
+ return (IInterfaceHost) part;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ protected static IFluidInterfaceHost getFluidInterfaceTE(TileEntity te, Direction face) {
+ if (te instanceof IFluidInterfaceHost) {
+ return (IFluidInterfaceHost) te;
+ } else if (te instanceof CableBusTileEntity) {
+ IPart part = ((CableBusTileEntity) te).getPart(face.getOpposite());
+ if (part instanceof IFluidInterfaceHost) {
+ return (IFluidInterfaceHost) part;
+ }
+ }
+ return null;
+ }
+
+ private boolean isSameGrid(IInterfaceHost target) {
+ if (this.self != null && target != null) {
+ DualityInterface other = target.getInterfaceDuality();
+ try {
+ AENetworkProxy proxy1 = Ae2Reflect.getInterfaceProxy(other);
+ AENetworkProxy proxy2 = Ae2Reflect.getInterfaceProxy(this.self);
+ if (proxy1.getGrid() == proxy2.getGrid()) {
+ return true;
+ }
+ } catch (GridAccessException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private boolean isSameGrid(IFluidInterfaceHost target) {
+ if (this.self != null && target != null) {
+ DualityFluidInterface other = target.getDualityFluidInterface();
+ try {
+ AENetworkProxy proxy1 = Ae2Reflect.getInterfaceProxy(other);
+ AENetworkProxy proxy2 = Ae2Reflect.getInterfaceProxy(this.self);
+ if (proxy1.getGrid() == proxy2.getGrid()) {
+ return true;
+ }
+ } catch (GridAccessException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ @Nonnull
+ public Iterator iterator() {
+ return new SlotIterator(
+ invFluids, invItems != null ? invItems.iterator() : Collections.emptyIterator());
+ }
+
+ private static class SlotIterator implements Iterator {
+
+ private final IFluidHandler tanks;
+ private final Iterator itemSlots;
+ private int nextSlotIndex = 0;
+
+ SlotIterator(IFluidHandler tanks, Iterator itemSlots) {
+ this.tanks = tanks;
+ this.itemSlots = itemSlots;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nextSlotIndex < tanks.getTanks() || itemSlots.hasNext();
+ }
+
+ @Override
+ public ItemSlot next() {
+ if (nextSlotIndex < tanks.getTanks()) {
+ FluidStack fluid = tanks.getFluidInTank(nextSlotIndex);
+ ItemSlot slot = new ItemSlot();
+ slot.setSlot(nextSlotIndex++);
+ slot.setItemStack(!fluid.isEmpty() ? 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/loader/ChannelLoader.java b/src/main/java/com/glodblock/github/loader/ChannelLoader.java
new file mode 100644
index 000000000..3a3978ed5
--- /dev/null
+++ b/src/main/java/com/glodblock/github/loader/ChannelLoader.java
@@ -0,0 +1,44 @@
+package com.glodblock.github.loader;
+
+import com.glodblock.github.network.NetworkManager;
+import com.glodblock.github.network.packets.CPacketDumpTank;
+import com.glodblock.github.network.packets.CPacketFluidCraftBtns;
+import com.glodblock.github.network.packets.CPacketLoadPattern;
+import com.glodblock.github.network.packets.IMessage;
+import com.glodblock.github.util.ModAndClassUtil;
+import net.minecraft.network.PacketBuffer;
+import net.minecraftforge.fml.network.NetworkEvent;
+
+import java.util.function.Supplier;
+
+public class ChannelLoader {
+
+ static int id = 0;
+
+ public static void load() {
+ registerPacket(new CPacketDumpTank());
+ registerPacket(new CPacketFluidCraftBtns());
+ if (ModAndClassUtil.JEI || ModAndClassUtil.REI) {
+ registerPacket(new CPacketLoadPattern());
+ }
+ }
+
+ private static > void registerPacket(MSG msg) {
+ NetworkManager.netHandler.registerMessage(
+ id ++,
+ msg.getPacketClass(),
+ ChannelLoader::encoder,
+ msg::fromBytes,
+ ChannelLoader::handle
+ );
+ }
+
+ public static > void encoder(MSG msg, PacketBuffer p) {
+ msg.toBytes(p);
+ }
+
+ public static > void handle(MSG msg, Supplier ctx) {
+ msg.onMessage(ctx);
+ }
+
+}
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..aca4f28c6
--- /dev/null
+++ b/src/main/java/com/glodblock/github/loader/FCBlocks.java
@@ -0,0 +1,37 @@
+package com.glodblock.github.loader;
+
+import com.glodblock.github.common.block.*;
+import com.glodblock.github.handler.RegistryHandler;
+import com.glodblock.github.util.NameConst;
+
+public class FCBlocks {
+
+ public static BlockFluidDiscretizer FLUID_DISCRETIZER;
+ //public static BlockFluidPatternEncoder FLUID_PATTERN_ENCODER;
+ public static BlockFluidPacketDecoder FLUID_PACKET_DECODER;
+ public static BlockIngredientBuffer INGREDIENT_BUFFER;
+ public static BlockLargeIngredientBuffer LARGE_INGREDIENT_BUFFER;
+ //public static BlockBurette BURETTE;
+ public static BlockDualInterface DUAL_INTERFACE;
+ //public static BlockFluidAssembler FLUID_ASSEMBLER;
+
+ public static void init(RegistryHandler regHandler) {
+ FLUID_DISCRETIZER = new BlockFluidDiscretizer();
+ //FLUID_PATTERN_ENCODER = new BlockFluidPatternEncoder();
+ FLUID_PACKET_DECODER = new BlockFluidPacketDecoder();
+ INGREDIENT_BUFFER = new BlockIngredientBuffer();
+ LARGE_INGREDIENT_BUFFER = new BlockLargeIngredientBuffer();
+ //BURETTE = new BlockBurette();
+ DUAL_INTERFACE = new BlockDualInterface();
+ //FLUID_ASSEMBLER = new BlockFluidAssembler();
+ regHandler.block(NameConst.BLOCK_FLUID_DISCRETIZER, FLUID_DISCRETIZER);
+ //regHandler.block(NameConst.BLOCK_FLUID_PATTERN_ENCODER, FLUID_PATTERN_ENCODER);
+ regHandler.block(NameConst.BLOCK_FLUID_PACKET_DECODER, FLUID_PACKET_DECODER);
+ regHandler.block(NameConst.BLOCK_INGREDIENT_BUFFER, INGREDIENT_BUFFER);
+ regHandler.block(NameConst.BLOCK_LARGE_INGREDIENT_BUFFER, LARGE_INGREDIENT_BUFFER);
+ //regHandler.block(NameConst.BLOCK_BURETTE, BURETTE);
+ regHandler.block(NameConst.BLOCK_DUAL_INTERFACE, DUAL_INTERFACE);
+ //regHandler.block(NameConst.BLOCK_FLUID_ASSEMBLER, FLUID_ASSEMBLER);
+ }
+
+}
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..30aecccd0
--- /dev/null
+++ b/src/main/java/com/glodblock/github/loader/FCItems.java
@@ -0,0 +1,49 @@
+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.item.Item;
+import net.minecraft.item.ItemGroup;
+import net.minecraft.item.ItemStack;
+
+import javax.annotation.Nonnull;
+
+public class FCItems {
+
+ public static final ItemGroup TAB_AE2FC = new ItemGroup(FluidCraft.MODID) {
+ @Nonnull
+ @Override
+ public ItemStack createIcon() {
+ return new ItemStack(FCBlocks.INGREDIENT_BUFFER);
+ }
+ };
+
+ public static ItemFluidDrop FLUID_DROP;
+ public static ItemFluidPacket FLUID_PACKET;
+ public static ItemFluidEncodedPattern DENSE_ENCODED_PATTERN;
+ //public static ItemFluidCraftEncodedPattern DENSE_CRAFT_ENCODED_PATTERN;
+ public static ItemPartDualInterface PART_DUAL_INTERFACE;
+ public static ItemPartFluidPatternTerminal PART_FLUID_PATTERN_TERMINAL;
+
+ public static void init(RegistryHandler regHandler) {
+ FLUID_DROP = new ItemFluidDrop();
+ FLUID_PACKET = new ItemFluidPacket();
+ DENSE_ENCODED_PATTERN = new ItemFluidEncodedPattern();
+ //DENSE_CRAFT_ENCODED_PATTERN = new ItemFluidCraftEncodedPattern();
+ PART_DUAL_INTERFACE = new ItemPartDualInterface();
+ PART_FLUID_PATTERN_TERMINAL = new ItemPartFluidPatternTerminal();
+ regHandler.item(NameConst.ITEM_FLUID_DROP, FLUID_DROP);
+ regHandler.item(NameConst.ITEM_FLUID_PACKET, FLUID_PACKET);
+ regHandler.item(NameConst.ITEM_DENSE_ENCODED_PATTERN, DENSE_ENCODED_PATTERN);
+ //regHandler.item(NameConst.ITEM_DENSE_CRAFT_ENCODED_PATTERN, DENSE_CRAFT_ENCODED_PATTERN);
+ regHandler.item(NameConst.ITEM_PART_DUAL_INTERFACE, PART_DUAL_INTERFACE);
+ regHandler.item(NameConst.ITEM_PART_FLUID_PATTERN_TERMINAL, PART_FLUID_PATTERN_TERMINAL);
+ }
+
+ public static Item.Properties defaultProps() {
+ return new Item.Properties().group(TAB_AE2FC);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/AESubScreenMixin.java b/src/main/java/com/glodblock/github/mixins/AESubScreenMixin.java
new file mode 100644
index 000000000..b12b24928
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/AESubScreenMixin.java
@@ -0,0 +1,40 @@
+package com.glodblock.github.mixins;
+
+import appeng.client.gui.implementations.AESubScreen;
+import com.glodblock.github.client.container.ContainerFluidPatternTerminal;
+import com.glodblock.github.common.part.PartFluidPatternTerminal;
+import com.glodblock.github.loader.FCItems;
+import net.minecraft.inventory.container.ContainerType;
+import net.minecraft.item.ItemStack;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Mutable;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(AESubScreen.class)
+public abstract class AESubScreenMixin {
+
+ @Final
+ @Mutable
+ @Shadow(remap = false)
+ private ContainerType> previousContainerType;
+ @Final
+ @Mutable
+ @Shadow(remap = false)
+ private ItemStack previousContainerIcon;
+
+ @Inject(
+ method = "",
+ at = @At("RETURN")
+ )
+ private void addExtendedGUI(Object containerHost, CallbackInfo ci) {
+ if (containerHost instanceof PartFluidPatternTerminal) {
+ previousContainerIcon = new ItemStack(FCItems.PART_FLUID_PATTERN_TERMINAL);
+ previousContainerType = ContainerFluidPatternTerminal.TYPE;
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/ApiCraftingMixin.java b/src/main/java/com/glodblock/github/mixins/ApiCraftingMixin.java
new file mode 100644
index 000000000..2c80ee073
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/ApiCraftingMixin.java
@@ -0,0 +1,33 @@
+package com.glodblock.github.mixins;
+
+import appeng.api.networking.crafting.ICraftingPatternDetails;
+import appeng.core.api.ApiCrafting;
+import com.glodblock.github.common.item.ItemFluidEncodedPattern;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(ApiCrafting.class)
+public abstract class ApiCraftingMixin {
+
+ @Inject(
+ method = "decodePattern",
+ at = @At("HEAD"),
+ cancellable = true,
+ remap = false
+ )
+ private void injectDecodePattern(ItemStack is, World world, boolean autoRecovery, CallbackInfoReturnable cir) {
+ if (is != null && is.getItem() instanceof ItemFluidEncodedPattern) {
+ ItemFluidEncodedPattern pattern = (ItemFluidEncodedPattern) is.getItem();
+ if (pattern.isEncodedPattern(is)) {
+ ICraftingPatternDetails d = pattern.getDetails(is);
+ cir.setReturnValue(d);
+ }
+ cir.cancel();
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/CraftConfirmContainerMixin.java b/src/main/java/com/glodblock/github/mixins/CraftConfirmContainerMixin.java
new file mode 100644
index 000000000..2a43627d7
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/CraftConfirmContainerMixin.java
@@ -0,0 +1,35 @@
+package com.glodblock.github.mixins;
+
+import appeng.api.networking.security.IActionHost;
+import appeng.container.AEBaseContainer;
+import appeng.container.me.crafting.CraftConfirmContainer;
+import com.glodblock.github.client.container.ContainerFluidPatternTerminal;
+import com.glodblock.github.common.part.PartFluidPatternTerminal;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.ContainerType;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+
+@Mixin(CraftConfirmContainer.class)
+public abstract class CraftConfirmContainerMixin extends AEBaseContainer {
+
+ public CraftConfirmContainerMixin(ContainerType> containerType, int id, PlayerInventory playerInventory, Object host) {
+ super(containerType, id, playerInventory, host);
+ }
+
+ @ModifyVariable(
+ method = "startJob",
+ at = @At(value = "STORE", ordinal = 0),
+ ordinal = 0,
+ remap = false
+ )
+ private ContainerType> addExtendedGUI(ContainerType> ct) {
+ IActionHost ah = this.getActionHost();
+ if (ah instanceof PartFluidPatternTerminal) {
+ return ContainerFluidPatternTerminal.TYPE;
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/CraftConfirmTableRendererMixin.java b/src/main/java/com/glodblock/github/mixins/CraftConfirmTableRendererMixin.java
new file mode 100644
index 000000000..b7a5ae92f
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/CraftConfirmTableRendererMixin.java
@@ -0,0 +1,43 @@
+package com.glodblock.github.mixins;
+
+import appeng.client.gui.me.crafting.CraftConfirmTableRenderer;
+import appeng.container.me.crafting.CraftingPlanSummaryEntry;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Overwrite;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(CraftConfirmTableRenderer.class)
+public abstract class CraftConfirmTableRendererMixin {
+
+ /**
+ * @author GlodBlock
+ * @reason Render drop as fluid
+ */
+ @Overwrite(
+ remap = false
+ )
+ protected ItemStack getEntryItem(CraftingPlanSummaryEntry entry) {
+ if (entry.getItem() != null && entry.getItem().getItem() instanceof ItemFluidDrop) {
+ FluidStack fluid = ItemFluidDrop.getFluidStack(entry.getItem());
+ if (!fluid.isEmpty()) {
+ return ItemFluidPacket.newDisplayStack(fluid);
+ }
+ }
+ return entry.getItem();
+ }
+
+ @Redirect(
+ method = "getEntryTooltip*",
+ at = @At(value = "INVOKE", target = "Lappeng/container/me/crafting/CraftingPlanSummaryEntry;getItem()Lnet/minecraft/item/ItemStack;"),
+ remap = false
+ )
+ private ItemStack changeItem(CraftingPlanSummaryEntry entry) {
+ return getEntryItem(entry);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/CraftingCpuMixin.java b/src/main/java/com/glodblock/github/mixins/CraftingCpuMixin.java
new file mode 100644
index 000000000..37299a6d8
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/CraftingCpuMixin.java
@@ -0,0 +1,136 @@
+package com.glodblock.github.mixins;
+
+import appeng.api.config.Actionable;
+import appeng.api.networking.IGrid;
+import appeng.api.networking.security.IActionSource;
+import appeng.api.networking.storage.IStorageGrid;
+import appeng.api.storage.IMEInventory;
+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.core.Api;
+import appeng.crafting.MECraftingInventory;
+import appeng.me.cluster.implementations.CraftingCPUCluster;
+import appeng.me.helpers.MachineSource;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.glodblock.github.loader.FCItems;
+import com.google.common.base.Preconditions;
+import net.minecraft.inventory.CraftingInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Overwrite;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(CraftingCPUCluster.class)
+public abstract class CraftingCpuMixin {
+
+ @Shadow(remap = false)
+ private boolean isComplete;
+ @Shadow(remap = false)
+ private MECraftingInventory inventory;
+ @Shadow(remap = false)
+ private MachineSource machineSrc;
+ @Shadow(remap = false)
+ protected abstract IGrid getGrid();
+ @Shadow(remap = false)
+ protected abstract void postChange(IAEItemStack diff, IActionSource src);
+ @Shadow(remap = false)
+ protected abstract void markDirty();
+
+ @Redirect(
+ method = "executeCrafting",
+ at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", ordinal = 0, remap = false),
+ remap = false
+ )
+ private long getFluidStackSize(IAEItemStack packet) {
+ if (packet.getDefinition() != null && !packet.getDefinition().isEmpty() && packet.getDefinition().getItem() instanceof ItemFluidDrop) {
+ return (long) Math.max(packet.getStackSize() / 1000D, 1);
+ } else return packet.getStackSize();
+ }
+
+ @Redirect(
+ method = "executeCrafting",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/inventory/CraftingInventory;getStackInSlot(I)Lnet/minecraft/item/ItemStack;"),
+ remap = false
+ )
+ private ItemStack removeFluidPackets(CraftingInventory inv, int index) {
+ ItemStack stack = inv.getStackInSlot(index);
+ if (stack != ItemStack.EMPTY && stack.getItem() instanceof ItemFluidPacket) {
+ FluidStack fluid = ItemFluidPacket.getFluidStack(stack);
+ return ItemFluidDrop.newStack(fluid);
+ }
+ else {
+ return stack;
+ }
+ }
+
+ @Redirect(
+ method = "executeCrafting",
+ at = @At(value = "INVOKE", target = "Lappeng/util/item/AEItemStack;fromItemStack(Lnet/minecraft/item/ItemStack;)Lappeng/util/item/AEItemStack;", ordinal = 0, remap = false),
+ remap = false
+ )
+ private AEItemStack wrapFluidPacketStack(ItemStack stack) {
+ if (stack.getItem() == FCItems.FLUID_PACKET) {
+ IAEItemStack dropStack = ItemFluidDrop.newAeStack(ItemFluidPacket.getFluidStack(stack));
+ if (dropStack != null) {
+ return (AEItemStack) dropStack;
+ }
+ }
+ return AEItemStack.fromItemStack(stack);
+ }
+
+ /**
+ * @author GlodBlock
+ * @reason Fix fluid storage
+ */
+ @Overwrite(
+ remap = false
+ )
+ private void storeItems() {
+ Preconditions.checkState(this.isComplete, "CPU should be complete to prevent re-insertion when dumping items");
+ final IGrid g = this.getGrid();
+
+ if (g == null) {
+ return;
+ }
+
+ final IStorageGrid sg = g.getCache( IStorageGrid.class );
+ final IMEInventory ii = sg.getInventory(Api.instance().storage().getStorageChannel(IItemStorageChannel.class));
+ final IMEInventory jj = sg.getInventory(Api.instance().storage().getStorageChannel(IFluidStorageChannel.class));
+
+ for (IAEItemStack is : this.inventory.getItemList()) {
+ this.postChange(is, this.machineSrc);
+
+ if (is.getItem() instanceof ItemFluidDrop ) {
+ IAEFluidStack drop = ItemFluidDrop.getAeFluidStack(is);
+ IAEFluidStack fluidRemainder = jj.injectItems(drop, Actionable.MODULATE, this.machineSrc);
+ if (fluidRemainder != null) {
+ is.setStackSize(fluidRemainder.getStackSize());
+ } else {
+ is.reset();
+ }
+ } else {
+ IAEItemStack remainder = ii.injectItems(is.copy(), Actionable.MODULATE, this.machineSrc);
+ if (remainder != null) {
+ is.setStackSize(remainder.getStackSize());
+ } else {
+ is.reset();
+ }
+ }
+ }
+
+ if (this.inventory.getItemList().isEmpty()) {
+ this.inventory = new MECraftingInventory();
+ }
+
+ this.markDirty();
+ }
+
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/CraftingGridCacheMixin.java b/src/main/java/com/glodblock/github/mixins/CraftingGridCacheMixin.java
new file mode 100644
index 000000000..1f9d5a797
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/CraftingGridCacheMixin.java
@@ -0,0 +1,20 @@
+package com.glodblock.github.mixins;
+
+import appeng.me.cache.CraftingGridCache;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(CraftingGridCache.class)
+public abstract class CraftingGridCacheMixin {
+
+ @Redirect(
+ method = "addCraftingOption",
+ at = @At(value = "INVOKE", target = "Lcom/google/common/base/Preconditions;checkArgument(ZLjava/lang/Object;)V", remap = false),
+ remap = false
+ )
+ private void removeDetailRestriction(boolean expression, Object errorMessage) {
+ // NO-OP
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/CraftingStatusTableRendererMixin.java b/src/main/java/com/glodblock/github/mixins/CraftingStatusTableRendererMixin.java
new file mode 100644
index 000000000..6df95e218
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/CraftingStatusTableRendererMixin.java
@@ -0,0 +1,44 @@
+package com.glodblock.github.mixins;
+
+import appeng.client.gui.me.crafting.CraftingStatusTableRenderer;
+import appeng.container.me.crafting.CraftingPlanSummaryEntry;
+import appeng.container.me.crafting.CraftingStatusEntry;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Overwrite;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(CraftingStatusTableRenderer.class)
+public abstract class CraftingStatusTableRendererMixin {
+
+ /**
+ * @author GlodBlock
+ * @reason Render drop as fluid
+ */
+ @Overwrite(
+ remap = false
+ )
+ protected ItemStack getEntryItem(CraftingStatusEntry entry) {
+ if (entry.getItem() != null && entry.getItem().getItem() instanceof ItemFluidDrop) {
+ FluidStack fluid = ItemFluidDrop.getFluidStack(entry.getItem());
+ if (!fluid.isEmpty()) {
+ return ItemFluidPacket.newDisplayStack(fluid);
+ }
+ }
+ return entry.getItem();
+ }
+
+ @Redirect(
+ method = "getEntryTooltip*",
+ at = @At(value = "INVOKE", target = "Lappeng/container/me/crafting/CraftingStatusEntry;getItem()Lnet/minecraft/item/ItemStack;", remap = false),
+ remap = false
+ )
+ private ItemStack changeItem(CraftingStatusEntry entry) {
+ return getEntryItem(entry);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/CraftingTreeNodeMixin.java b/src/main/java/com/glodblock/github/mixins/CraftingTreeNodeMixin.java
new file mode 100644
index 000000000..395b251d3
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/CraftingTreeNodeMixin.java
@@ -0,0 +1,87 @@
+package com.glodblock.github.mixins;
+
+import appeng.api.storage.data.IAEItemStack;
+import appeng.crafting.CraftingTreeNode;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+import org.spongepowered.asm.mixin.injection.Slice;
+
+import static org.objectweb.asm.Opcodes.GETFIELD;
+import static org.objectweb.asm.Opcodes.PUTFIELD;
+
+@Mixin(CraftingTreeNode.class)
+public abstract class CraftingTreeNodeMixin {
+
+ @Redirect(
+ method = "request",
+ at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false),
+ slice = @Slice(
+ from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD),
+ to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD)
+ ),
+ remap = false
+ )
+ private long getCraftingByteCost(IAEItemStack stack) {
+ return stack.getItem() instanceof ItemFluidDrop
+ ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize();
+ }
+
+ @Redirect(
+ method = "request",
+ at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false),
+ slice = @Slice(
+ from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD, ordinal = 1),
+ to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD, ordinal = 1)
+ ),
+ remap = false
+ )
+ private long getCraftingByteCost2(IAEItemStack stack) {
+ return stack.getItem() instanceof ItemFluidDrop
+ ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize();
+ }
+
+ @Redirect(
+ method = "request",
+ at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false),
+ slice = @Slice(
+ from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD, ordinal = 2),
+ to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD, ordinal = 2)
+ ),
+ remap = false
+ )
+ private long getCraftingByteCost3(IAEItemStack stack) {
+ return stack.getItem() instanceof ItemFluidDrop
+ ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize();
+ }
+
+ @Redirect(
+ method = "request",
+ at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false),
+ slice = @Slice(
+ from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD, ordinal = 3),
+ to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD, ordinal = 3)
+ ),
+ remap = false
+ )
+ private long getCraftingByteCost4(IAEItemStack stack) {
+ return stack.getItem() instanceof ItemFluidDrop
+ ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize();
+ }
+
+ @Redirect(
+ method = "request",
+ at = @At(value = "INVOKE", target = "Lappeng/api/storage/data/IAEItemStack;getStackSize()J", remap = false),
+ slice = @Slice(
+ from = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = GETFIELD, ordinal = 4),
+ to = @At(value = "FIELD", target = "Lappeng/crafting/CraftingTreeNode;bytes:I", opcode = PUTFIELD, ordinal = 4)
+ ),
+ remap = false
+ )
+ private long getCraftingByteCost5(IAEItemStack stack) {
+ return stack.getItem() instanceof ItemFluidDrop
+ ? (long)Math.ceil(stack.getStackSize() / 1000D) : stack.getStackSize();
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/DualityInterfaceMixin.java b/src/main/java/com/glodblock/github/mixins/DualityInterfaceMixin.java
new file mode 100644
index 000000000..bbc35d5e3
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/DualityInterfaceMixin.java
@@ -0,0 +1,84 @@
+package com.glodblock.github.mixins;
+
+import appeng.helpers.DualityInterface;
+import appeng.util.InventoryAdaptor;
+import com.glodblock.github.coreutil.ExtendedInterface;
+import com.glodblock.github.inventory.FluidConvertingInventoryAdaptor;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.Direction;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.Redirect;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(DualityInterface.class)
+public abstract class DualityInterfaceMixin implements ExtendedInterface {
+
+ private boolean fluidPacket;
+ private boolean allowSplitting;
+ private int blockModeEx;
+
+ @Inject(
+ method = "writeToNBT",
+ at = @At("HEAD"),
+ remap = false
+ )
+ public void writeToNBT(CompoundNBT data, CallbackInfo ci) {
+ data.putBoolean("fluidPacket", fluidPacket);
+ data.putBoolean("allowSplitting", allowSplitting);
+ data.putInt("blockModeEx", blockModeEx);
+ }
+
+ @Inject(
+ method = "readFromNBT",
+ at = @At("HEAD"),
+ remap = false
+ )
+ public void readFromNBT(CompoundNBT data, CallbackInfo ci) {
+ fluidPacket = data.getBoolean("fluidPacket");
+ allowSplitting = data.getBoolean("allowSplitting");
+ blockModeEx = data.getInt("blockModeEx");;
+ }
+
+ @Redirect(
+ method = {"pushItemsOut", "pushPattern", "isBusy"},
+ at = @At(value = "INVOKE", target = "Lappeng/util/InventoryAdaptor;getAdaptor(Lnet/minecraft/tileentity/TileEntity;Lnet/minecraft/util/Direction;)Lappeng/util/InventoryAdaptor;", remap = false),
+ remap = false
+ )
+ private InventoryAdaptor wrapInventoryAdaptor(TileEntity cap, Direction te) {
+ return cap != null ? FluidConvertingInventoryAdaptor.wrap(cap, te) : null;
+ }
+
+ @Override
+ public boolean getFluidPacketMode() {
+ return fluidPacket;
+ }
+
+ @Override
+ public void setFluidPacketMode(boolean value) {
+ this.fluidPacket = value;
+ }
+
+ @Override
+ public boolean getSplittingMode() {
+ return allowSplitting;
+ }
+
+ @Override
+ public void setSplittingMode(boolean value) {
+ this.allowSplitting = value;
+ }
+
+ @Override
+ public int getExtendedBlockMode() {
+ return blockModeEx;
+ }
+
+ @Override
+ public void setExtendedBlockMode(int value) {
+ this.blockModeEx = value;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/EncodedPatternBakedModelMixin.java b/src/main/java/com/glodblock/github/mixins/EncodedPatternBakedModelMixin.java
new file mode 100644
index 000000000..7c518f5b8
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/EncodedPatternBakedModelMixin.java
@@ -0,0 +1,31 @@
+package com.glodblock.github.mixins;
+
+import appeng.items.misc.EncodedPatternItem;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(targets = "appeng.client.render.crafting.EncodedPatternBakedModel$CustomOverrideList")
+public abstract class EncodedPatternBakedModelMixin {
+
+ @Redirect(
+ method = "getOverrideModel(Lnet/minecraft/client/renderer/model/IBakedModel;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/entity/LivingEntity;)Lnet/minecraft/client/renderer/model/IBakedModel;",
+ at = @At(value = "INVOKE", target = "Lappeng/items/misc/EncodedPatternItem;getOutput(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;"),
+ remap = false
+ )
+ private ItemStack rendDropAsFluid(EncodedPatternItem pattern, ItemStack item) {
+ ItemStack output = pattern.getOutput(item);
+ if (output != null && output.getItem() instanceof ItemFluidDrop) {
+ FluidStack fluid = ItemFluidDrop.getFluidStack(output);
+ if (!fluid.isEmpty()) {
+ return ItemFluidPacket.newDisplayStack(fluid);
+ }
+ }
+ return output;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/InterfaceSlotMixin.java b/src/main/java/com/glodblock/github/mixins/InterfaceSlotMixin.java
new file mode 100644
index 000000000..431f61471
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/InterfaceSlotMixin.java
@@ -0,0 +1,32 @@
+package com.glodblock.github.mixins;
+
+import appeng.client.gui.me.interfaceterminal.InterfaceSlot;
+import appeng.items.misc.EncodedPatternItem;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(InterfaceSlot.class)
+public class InterfaceSlotMixin {
+
+ @Redirect(
+ method = "getDisplayStack",
+ at = @At(value = "INVOKE", target = "Lappeng/items/misc/EncodedPatternItem;getOutput(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;"),
+ remap = false
+ )
+ private ItemStack rendDropAsFluid(EncodedPatternItem pattern, ItemStack item) {
+ ItemStack output = pattern.getOutput(item);
+ if (output != null && output.getItem() instanceof ItemFluidDrop) {
+ FluidStack fluid = ItemFluidDrop.getFluidStack(output);
+ if (!fluid.isEmpty()) {
+ return ItemFluidPacket.newDisplayStack(fluid);
+ }
+ }
+ return output;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/InterfaceTerminalContainerMixin.java b/src/main/java/com/glodblock/github/mixins/InterfaceTerminalContainerMixin.java
new file mode 100644
index 000000000..4c970dfac
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/InterfaceTerminalContainerMixin.java
@@ -0,0 +1,98 @@
+package com.glodblock.github.mixins;
+
+import appeng.api.config.Settings;
+import appeng.api.config.YesNo;
+import appeng.api.networking.IGrid;
+import appeng.api.networking.IGridNode;
+import appeng.container.AEBaseContainer;
+import appeng.container.implementations.InterfaceTerminalContainer;
+import appeng.core.sync.BasePacket;
+import appeng.helpers.DualityInterface;
+import appeng.helpers.IInterfaceHost;
+import appeng.parts.misc.InterfacePart;
+import appeng.tile.misc.InterfaceTileEntity;
+import com.glodblock.github.common.part.PartDualInterface;
+import com.glodblock.github.common.tile.TileDualInterface;
+import com.glodblock.github.util.Ae2Reflect;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.inventory.container.ContainerType;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Overwrite;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import javax.annotation.Nullable;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+@Mixin(InterfaceTerminalContainer.class)
+public abstract class InterfaceTerminalContainerMixin extends AEBaseContainer {
+
+ public InterfaceTerminalContainerMixin(ContainerType> containerType, int id, PlayerInventory playerInventory, Object host) {
+ super(containerType, id, playerInventory, host);
+ }
+
+ @Shadow(remap = false)
+ protected abstract IGrid getGrid();
+ @Shadow(remap = false)
+ protected abstract void sendIncrementalUpdate(Consumer packetSender);
+ @Shadow(remap = false)
+ protected abstract void sendFullUpdate(@Nullable IGrid grid, Consumer packetSender);
+ @Final
+ @Shadow(remap = false)
+ @SuppressWarnings("rawtypes")
+ private Map diList;
+
+
+ /**
+ * @author GlodBlock
+ * @reason Add dual interface to interface terminal
+ */
+ @Overwrite
+ public void detectAndSendChanges() {
+ if (!this.isClient()) {
+ super.detectAndSendChanges();
+ IGrid grid = this.getGrid();
+ Object state = Ae2Reflect.genVisitorState();
+ if (grid != null) {
+ Ae2Reflect.visitInterfaceHost((InterfaceTerminalContainer)(Object) this, grid, InterfaceTileEntity.class, state);
+ Ae2Reflect.visitInterfaceHost((InterfaceTerminalContainer)(Object) this, grid, InterfacePart.class, state);
+ Ae2Reflect.visitInterfaceHost((InterfaceTerminalContainer)(Object) this, grid, TileDualInterface.class, state);
+ Ae2Reflect.visitInterfaceHost((InterfaceTerminalContainer)(Object) this, grid, PartDualInterface.class, state);
+ }
+ if (Ae2Reflect.getVisitorStateTotal(state) == this.diList.size() && !Ae2Reflect.getVisitorStateUpdate(state)) {
+ this.sendIncrementalUpdate(this::sendPacketToClient);
+ } else {
+ this.sendFullUpdate(grid, this::sendPacketToClient);
+ }
+ }
+ }
+
+ @Inject(
+ method = "sendFullUpdate",
+ at = @At(value = "INVOKE", target = "Lappeng/api/networking/IGrid;getMachines(Ljava/lang/Class;)Lappeng/api/networking/IMachineSet;", ordinal = 0),
+ remap = false
+ )
+ @SuppressWarnings("unchecked")
+ private void sendExtendUpdateInfo(IGrid grid, Consumer packetSender, CallbackInfo ci) {
+ for (final IGridNode gn : grid.getMachines(TileDualInterface.class)) {
+ final IInterfaceHost ih = (IInterfaceHost) gn.getMachine();
+ final DualityInterface dual = ih.getInterfaceDuality();
+ if (gn.isActive() && dual.getConfigManager().getSetting(Settings.INTERFACE_TERMINAL) == YesNo.YES) {
+ this.diList.put(ih, Ae2Reflect.genInvTracker(dual, dual.getPatterns(), dual.getTermName()));
+ }
+ }
+ for (final IGridNode gn : grid.getMachines(PartDualInterface.class)) {
+ final IInterfaceHost ih = (IInterfaceHost) gn.getMachine();
+ final DualityInterface dual = ih.getInterfaceDuality();
+ if (gn.isActive() && dual.getConfigManager().getSetting(Settings.INTERFACE_TERMINAL) == YesNo.YES) {
+ this.diList.put(ih, Ae2Reflect.genInvTracker(dual, dual.getPatterns(), dual.getTermName()));
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/PatternSlotPacketMixin.java b/src/main/java/com/glodblock/github/mixins/PatternSlotPacketMixin.java
new file mode 100644
index 000000000..4d04dddb7
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/PatternSlotPacketMixin.java
@@ -0,0 +1,31 @@
+package com.glodblock.github.mixins;
+
+import appeng.core.sync.network.INetworkInfo;
+import appeng.core.sync.packets.PatternSlotPacket;
+import com.glodblock.github.client.container.ContainerFluidPatternTerminal;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.entity.player.ServerPlayerEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(PatternSlotPacket.class)
+public abstract class PatternSlotPacketMixin {
+
+ @Inject(
+ method = "serverPacketData",
+ at = @At("HEAD"),
+ cancellable = true,
+ remap = false
+ )
+ private void addFluidPatternHandler(INetworkInfo manager, PlayerEntity player, CallbackInfo ci) {
+ ServerPlayerEntity sender = (ServerPlayerEntity) player;
+ if (sender.openContainer instanceof ContainerFluidPatternTerminal) {
+ ContainerFluidPatternTerminal patternTerminal = (ContainerFluidPatternTerminal)sender.openContainer;
+ patternTerminal.craftOrGetItem((PatternSlotPacket)(Object) this);
+ ci.cancel();
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/RestrictedInputSlotMixin.java b/src/main/java/com/glodblock/github/mixins/RestrictedInputSlotMixin.java
new file mode 100644
index 000000000..8fc0dfc13
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/RestrictedInputSlotMixin.java
@@ -0,0 +1,32 @@
+package com.glodblock.github.mixins;
+
+import appeng.container.slot.RestrictedInputSlot;
+import appeng.items.misc.EncodedPatternItem;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(RestrictedInputSlot.class)
+public abstract class RestrictedInputSlotMixin {
+
+ @Redirect(
+ method = "getDisplayStack",
+ at = @At(value = "INVOKE", target = "Lappeng/items/misc/EncodedPatternItem;getOutput(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;"),
+ remap = false
+ )
+ private ItemStack renderDropAsFluid(EncodedPatternItem pattern, ItemStack item) {
+ ItemStack output = pattern.getOutput(item);
+ if (output != null && output.getItem() instanceof ItemFluidDrop) {
+ FluidStack fluid = ItemFluidDrop.getFluidStack(output);
+ if (!fluid.isEmpty()) {
+ return ItemFluidPacket.newDisplayStack(fluid);
+ }
+ }
+ return output;
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/mixins/TMixin.java b/src/main/java/com/glodblock/github/mixins/TMixin.java
new file mode 100644
index 000000000..64c081a3b
--- /dev/null
+++ b/src/main/java/com/glodblock/github/mixins/TMixin.java
@@ -0,0 +1,32 @@
+package com.glodblock.github.mixins;
+
+import net.minecraft.client.Minecraft;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(Minecraft.class)
+public abstract class TMixin {
+
+ @Inject(
+ method = "isMultiplayerEnabled",
+ at = @At("HEAD"),
+ cancellable = true
+ )
+ private void z(CallbackInfoReturnable cir) {
+ cir.setReturnValue(true);
+ cir.cancel();
+ }
+
+ @Inject(
+ method = "isChatEnabled",
+ at = @At("HEAD"),
+ cancellable = true
+ )
+ private void x(CallbackInfoReturnable cir) {
+ cir.setReturnValue(true);
+ cir.cancel();
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/network/NetworkManager.java b/src/main/java/com/glodblock/github/network/NetworkManager.java
new file mode 100644
index 000000000..a9a02d1d5
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/NetworkManager.java
@@ -0,0 +1,16 @@
+package com.glodblock.github.network;
+
+import com.glodblock.github.FluidCraft;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.fml.network.NetworkRegistry;
+import net.minecraftforge.fml.network.simple.SimpleChannel;
+
+public class NetworkManager {
+
+ private static final ResourceLocation channel = FluidCraft.resource("network");
+
+ public static SimpleChannel netHandler = NetworkRegistry.newSimpleChannel(
+ channel, ()-> "v1.0", s -> true, s -> true
+ );
+
+}
diff --git a/src/main/java/com/glodblock/github/network/packets/CPacketDumpTank.java b/src/main/java/com/glodblock/github/network/packets/CPacketDumpTank.java
new file mode 100644
index 000000000..9e05d571f
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/packets/CPacketDumpTank.java
@@ -0,0 +1,51 @@
+package com.glodblock.github.network.packets;
+
+import com.glodblock.github.interfaces.TankDumpable;
+import net.minecraft.entity.player.ServerPlayerEntity;
+import net.minecraft.network.PacketBuffer;
+import net.minecraftforge.fml.network.NetworkEvent;
+
+import java.util.function.Supplier;
+
+public class CPacketDumpTank implements IMessage {
+
+ private int index;
+
+ public CPacketDumpTank(int index) {
+ this.index = index;
+ }
+
+ public CPacketDumpTank() {
+ // NO-OP
+ }
+
+ @Override
+ public void toBytes(PacketBuffer buf) {
+ buf.writeShort(index);
+ }
+
+ @Override
+ public CPacketDumpTank fromBytes(PacketBuffer buf) {
+ CPacketDumpTank dup = new CPacketDumpTank();
+ dup.index = buf.readShort();
+ return dup;
+ }
+
+ @Override
+ public void onMessage(Supplier ctx) {
+ ServerPlayerEntity player = ctx.get().getSender();
+ if (player != null) {
+ ctx.get().enqueueWork(() -> {
+ if (player.openContainer instanceof TankDumpable) {
+ ((TankDumpable)player.openContainer).dumpTank(this.index);
+ }
+ });
+ }
+ }
+
+ @Override
+ public Class getPacketClass() {
+ return CPacketDumpTank.class;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/network/packets/CPacketFluidCraftBtns.java b/src/main/java/com/glodblock/github/network/packets/CPacketFluidCraftBtns.java
new file mode 100644
index 000000000..ffc79e268
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/packets/CPacketFluidCraftBtns.java
@@ -0,0 +1,86 @@
+package com.glodblock.github.network.packets;
+
+import com.glodblock.github.interfaces.ConfigData;
+import net.minecraft.entity.player.ServerPlayerEntity;
+import net.minecraft.network.PacketBuffer;
+import net.minecraftforge.fml.network.NetworkEvent;
+
+import java.util.function.Supplier;
+
+public class CPacketFluidCraftBtns implements IMessage {
+
+ private String id;
+ private int value;
+ private OType type;
+
+ public CPacketFluidCraftBtns(String id) {
+ this.id = id;
+ this.value = -1;
+ this.type = OType.VOID;
+ }
+
+ public CPacketFluidCraftBtns(String id, boolean value) {
+ this.id = id;
+ this.value = value ? 1 : 0;
+ this.type = OType.BOOLEAN;
+ }
+
+ public CPacketFluidCraftBtns(String id, int value) {
+ this.id = id;
+ this.value = value;
+ this.type = OType.INT;
+ }
+
+ public CPacketFluidCraftBtns() {
+ // NO-OP
+ }
+
+ @Override
+ public void toBytes(PacketBuffer buf) {
+ buf.writeString(id);
+ buf.writeVarInt(value);
+ buf.writeByte(type.ordinal());
+ }
+
+ @Override
+ public CPacketFluidCraftBtns fromBytes(PacketBuffer buf) {
+ CPacketFluidCraftBtns dup = new CPacketFluidCraftBtns();
+ dup.id = buf.readString(32767);
+ dup.value = buf.readVarInt();
+ dup.type = OType.values()[buf.readByte()];
+ return dup;
+ }
+
+ @Override
+ public void onMessage(Supplier ctx) {
+ ServerPlayerEntity player = ctx.get().getSender();
+ if (player != null) {
+ ctx.get().enqueueWork(() -> {
+ if (player.openContainer instanceof ConfigData) {
+ switch (type) {
+ case INT:
+ ((ConfigData) player.openContainer).set(id, value);
+ break;
+ case BOOLEAN:
+ ((ConfigData) player.openContainer).set(id, value == 1);
+ break;
+ case VOID:
+ ((ConfigData) player.openContainer).set(id, null);
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public Class getPacketClass() {
+ return CPacketFluidCraftBtns.class;
+ }
+
+ enum OType {
+ INT,
+ BOOLEAN,
+ VOID
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/network/packets/CPacketLoadPattern.java b/src/main/java/com/glodblock/github/network/packets/CPacketLoadPattern.java
new file mode 100644
index 000000000..12183e1ec
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/packets/CPacketLoadPattern.java
@@ -0,0 +1,106 @@
+package com.glodblock.github.network.packets;
+
+import com.glodblock.github.interfaces.PatternConsumer;
+import com.glodblock.github.util.FCUtil;
+import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import net.minecraft.entity.player.ServerPlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.network.PacketBuffer;
+import net.minecraftforge.fml.network.NetworkEvent;
+
+import java.util.function.Supplier;
+
+public class CPacketLoadPattern implements IMessage {
+
+ private ItemStack[] output;
+ private Int2ObjectMap crafting;
+ private boolean compress;
+ private static final int SLOT_SIZE = 16;
+
+ public CPacketLoadPattern(Int2ObjectMap crafting, ItemStack[] output, boolean compress) {
+ this.crafting = crafting;
+ this.output = output;
+ this.compress = compress;
+ }
+
+ public CPacketLoadPattern() {
+ // NO-OP
+ }
+
+ @Override
+ public void toBytes(PacketBuffer buf) {
+ buf.writeBoolean(compress);
+ CompoundNBT msg = new CompoundNBT();
+ for (int index : crafting.keySet()) {
+ writeItemArray(msg, crafting.get(index), index + "#");
+ }
+ writeItemArray(msg, output, "o");
+ FCUtil.writeNBTToBytes(buf, msg);
+ }
+
+ @Override
+ public CPacketLoadPattern fromBytes(PacketBuffer buf) {
+ CPacketLoadPattern dup = new CPacketLoadPattern();
+ dup.crafting = new Int2ObjectArrayMap<>();
+ dup.compress = buf.readBoolean();
+ CompoundNBT msg = FCUtil.readNBTFromBytes(buf);
+ for (int i = 0; i < SLOT_SIZE; i ++) {
+ if (msg.contains(i + "#")) {
+ dup.crafting.put(i, readItemArray(msg, i + "#"));
+ }
+ }
+ dup.output = readItemArray(msg, "o");
+ return dup;
+ }
+
+ @Override
+ public void onMessage(Supplier ctx) {
+ ServerPlayerEntity player = ctx.get().getSender();
+ if (player != null) {
+ ctx.get().enqueueWork(() -> {
+ if (player.openContainer instanceof PatternConsumer) {
+ ((PatternConsumer) player.openContainer).acceptPattern(this.crafting, this.output, this.compress);
+ }
+ });
+ }
+ }
+
+ private void writeItemArray(CompoundNBT nbt, ItemStack[] itemList, String key) {
+ CompoundNBT dict = new CompoundNBT();
+ dict.putShort("l", (short) (itemList == null ? 0 : itemList.length));
+ if (itemList != null) {
+ int cnt = 0;
+ for (ItemStack item : itemList) {
+ CompoundNBT itemTag = new CompoundNBT();
+ if (item != null) {
+ item.write(itemTag);
+ dict.put(cnt + "#", itemTag);
+ cnt ++;
+ }
+ }
+ dict.putShort("l", (short) cnt);
+ }
+ nbt.put(key, dict);
+ }
+
+ private ItemStack[] readItemArray(CompoundNBT nbt, String key) {
+ CompoundNBT dict = nbt.getCompound(key);
+ short len = dict.getShort("l");
+ if (len == 0) {
+ return new ItemStack[0];
+ } else {
+ ItemStack[] itemList = new ItemStack[len];
+ for (int i = 0; i < len; i ++) {
+ itemList[i] = ItemStack.read(dict.getCompound(i + "#"));
+ }
+ return itemList;
+ }
+ }
+
+ @Override
+ public Class getPacketClass() {
+ return CPacketLoadPattern.class;
+ }
+}
diff --git a/src/main/java/com/glodblock/github/network/packets/IMessage.java b/src/main/java/com/glodblock/github/network/packets/IMessage.java
new file mode 100644
index 000000000..cb6ac5198
--- /dev/null
+++ b/src/main/java/com/glodblock/github/network/packets/IMessage.java
@@ -0,0 +1,18 @@
+package com.glodblock.github.network.packets;
+
+import net.minecraft.network.PacketBuffer;
+import net.minecraftforge.fml.network.NetworkEvent;
+
+import java.util.function.Supplier;
+
+public interface IMessage {
+
+ void toBytes(PacketBuffer buf);
+
+ MSG fromBytes(PacketBuffer buf);
+
+ void onMessage(Supplier ctx);
+
+ Class getPacketClass();
+
+}
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..26bcfeaf4
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/Ae2Reflect.java
@@ -0,0 +1,178 @@
+package com.glodblock.github.util;
+
+import appeng.api.definitions.IItemDefinition;
+import appeng.api.networking.IGrid;
+import appeng.container.implementations.InterfaceTerminalContainer;
+import appeng.fluids.helper.DualityFluidInterface;
+import appeng.helpers.DualityInterface;
+import appeng.me.helpers.AENetworkProxy;
+import appeng.recipes.game.DisassembleRecipe;
+import appeng.util.inv.ItemSlot;
+import net.minecraft.inventory.CraftingInventory;
+import net.minecraft.inventory.container.Container;
+import net.minecraft.item.crafting.IRecipe;
+import net.minecraft.item.crafting.SpecialRecipeSerializer;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraftforge.items.IItemHandler;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Function;
+
+public class Ae2Reflect {
+
+ private static final Field fDualInterface_gridProxy;
+ private static final Field fDualityFluidInterface_gridProxy;
+ private static final Field fDisassembleRecipe_nonCellMappings;
+ private static final Field fInterfaceTerminalContainer_total;
+ private static final Field fInterfaceTerminalContainer_forceFullUpdate;
+ private static final Field fSpecialRecipeSerializer_field_222176_t;
+ private static final Field fCraftingInventory_eventHandler;
+ private static final Method mItemSlot_setExtractable;
+ private static final Method mInterfaceTerminalContainer_visitInterfaceHosts;
+ private static final Constructor> cInterfaceTerminalContainer_VisitorState;
+ private static final Constructor> cInterfaceTerminalContainer_InvTracker;
+
+ static {
+ try {
+ fDualInterface_gridProxy = reflectField(DualityInterface.class, "gridProxy");
+ fDualityFluidInterface_gridProxy = reflectField(DualityFluidInterface.class, "gridProxy");
+ fDisassembleRecipe_nonCellMappings = reflectField(DisassembleRecipe.class, "nonCellMappings");
+ fInterfaceTerminalContainer_total = reflectField(Class.forName("appeng.container.implementations.InterfaceTerminalContainer$VisitorState"), "total");
+ fInterfaceTerminalContainer_forceFullUpdate = reflectField(Class.forName("appeng.container.implementations.InterfaceTerminalContainer$VisitorState"), "forceFullUpdate");
+ fSpecialRecipeSerializer_field_222176_t = reflectField(SpecialRecipeSerializer.class, "field_222176_t");
+ fCraftingInventory_eventHandler = reflectField(CraftingInventory.class, "eventHandler", "field_70465_c");
+ mItemSlot_setExtractable = reflectMethod(ItemSlot.class, "setExtractable", boolean.class);
+ mInterfaceTerminalContainer_visitInterfaceHosts = reflectMethod(InterfaceTerminalContainer.class, "visitInterfaceHosts",
+ IGrid.class, Class.class, Class.forName("appeng.container.implementations.InterfaceTerminalContainer$VisitorState"));
+ cInterfaceTerminalContainer_VisitorState = Class
+ .forName("appeng.container.implementations.InterfaceTerminalContainer$VisitorState")
+ .getDeclaredConstructor();
+ cInterfaceTerminalContainer_VisitorState.setAccessible(true);
+ cInterfaceTerminalContainer_InvTracker = Class
+ .forName("appeng.container.implementations.InterfaceTerminalContainer$InvTracker")
+ .getDeclaredConstructor(DualityInterface.class, IItemHandler.class, ITextComponent.class);
+ cInterfaceTerminalContainer_InvTracker.setAccessible(true);
+ } 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 {
+ return reflectMethod(owner, new String[]{name}, paramTypes);
+ }
+
+ @SuppressWarnings("all")
+ public static Method reflectMethod(Class> owner, String[] names, Class>... paramTypes) throws NoSuchMethodException {
+ Method m = null;
+ for (String name : names) {
+ try {
+ m = owner.getDeclaredMethod(name, paramTypes);
+ if (m != null) break;
+ }
+ catch (NoSuchMethodException ignore) {
+ }
+ }
+ if (m == null) throw new NoSuchMethodException("Can't find field from " + Arrays.toString(names));
+ m.setAccessible(true);
+ return m;
+ }
+
+ @SuppressWarnings("all")
+ 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 AENetworkProxy getInterfaceProxy(DualityInterface owner) {
+ return readField(owner, fDualInterface_gridProxy);
+ }
+
+ public static AENetworkProxy getInterfaceProxy(DualityFluidInterface owner) {
+ return readField(owner, fDualityFluidInterface_gridProxy);
+ }
+
+ 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 Object genVisitorState() {
+ try {
+ return cInterfaceTerminalContainer_VisitorState.newInstance();
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to invoke constructor: " + cInterfaceTerminalContainer_VisitorState, e);
+ }
+ }
+
+ public static Object genInvTracker(DualityInterface dual, IItemHandler patterns, ITextComponent name) {
+ try {
+ return cInterfaceTerminalContainer_InvTracker.newInstance(dual, patterns, name);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to invoke constructor: " + cInterfaceTerminalContainer_VisitorState, e);
+ }
+ }
+
+ public static void visitInterfaceHost(InterfaceTerminalContainer owner, IGrid grid, Class> machineClass, Object state) {
+ try {
+ mInterfaceTerminalContainer_visitInterfaceHosts.invoke(owner, grid, machineClass, state);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to invoke method: " + mInterfaceTerminalContainer_visitInterfaceHosts, e);
+ }
+ }
+
+ public static int getVisitorStateTotal(Object owner) {
+ return readField(owner, fInterfaceTerminalContainer_total);
+ }
+
+ public static boolean getVisitorStateUpdate(Object owner) {
+ return readField(owner, fInterfaceTerminalContainer_forceFullUpdate);
+ }
+
+ public static > Function getRecipeFactory(SpecialRecipeSerializer own) {
+ return readField(own, fSpecialRecipeSerializer_field_222176_t);
+ }
+
+ public static Container getContainer(CraftingInventory own) {
+ return readField(own, fCraftingInventory_eventHandler);
+ }
+
+}
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..6988a31cd
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/Ae2ReflectClient.java
@@ -0,0 +1,60 @@
+package com.glodblock.github.util;
+
+import appeng.client.gui.AEBaseScreen;
+import appeng.client.gui.ScreenRegistration;
+import appeng.client.gui.me.common.MEMonitorableScreen;
+import appeng.client.gui.style.TerminalStyle;
+import appeng.client.render.DelegateBakedModel;
+import appeng.container.AEBaseContainer;
+import com.google.common.collect.ImmutableMap;
+import net.minecraft.client.renderer.model.IBakedModel;
+import net.minecraft.inventory.container.ContainerType;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+@SuppressWarnings("unchecked")
+public class Ae2ReflectClient {
+
+ private static final Method mScreenRegistration_register;
+ private static final Field fMEMonitorableScreen_style;
+ private static final Constructor extends DelegateBakedModel> cEncodedPatternBakedModel;
+
+ static {
+ try {
+ mScreenRegistration_register = Ae2Reflect.reflectMethod(ScreenRegistration.class, "register",
+ ContainerType.class, ScreenRegistration.StyledScreenFactory.class, String.class
+ );
+ fMEMonitorableScreen_style = Ae2Reflect.reflectField(MEMonitorableScreen.class, "style");
+ cEncodedPatternBakedModel = (Constructor extends DelegateBakedModel>)Class
+ .forName("appeng.client.render.crafting.EncodedPatternBakedModel")
+ .getDeclaredConstructor(IBakedModel.class);
+ cEncodedPatternBakedModel.setAccessible(true);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to initialize AE2 reflection hacks!", e);
+ }
+ }
+
+ public static > void registerAEGui(ContainerType type, ScreenRegistration.StyledScreenFactory factory, String stylePath) {
+ try {
+ mScreenRegistration_register.invoke(null, type, factory, stylePath);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to invoke method: " + mScreenRegistration_register, e);
+ }
+ }
+
+ public static IBakedModel bakeEncodedPatternModel(IBakedModel baseModel) {
+ try {
+ return cEncodedPatternBakedModel.newInstance(baseModel);
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to invoke constructor: " + cEncodedPatternBakedModel, e);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static TerminalStyle getGuiStyle(MEMonitorableScreen gui) {
+ return Ae2Reflect.readField(gui, fMEMonitorableScreen_style);
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/util/ConfigSet.java b/src/main/java/com/glodblock/github/util/ConfigSet.java
new file mode 100644
index 000000000..f0918acd5
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/ConfigSet.java
@@ -0,0 +1,38 @@
+package com.glodblock.github.util;
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public final class ConfigSet {
+
+ private final Object2ObjectMap> MAP_IN = new Object2ObjectOpenHashMap<>();
+ private final Object2ObjectMap> MAP_OUT = new Object2ObjectOpenHashMap<>();
+
+ public ConfigSet addConfig(String id, Consumer> in, Supplier> out) {
+ MAP_IN.put(id, in);
+ MAP_OUT.put(id, out);
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void setConfig(String id, T value) {
+ if (MAP_IN.containsKey(id)) {
+ ((Consumer) MAP_IN.get(id)).accept(value);
+ } else {
+ throw new IllegalArgumentException("This config id doesn't exist: " + id);
+ }
+ }
+
+ public Object getConfig(String id) {
+ if (MAP_OUT.containsKey(id)) {
+ return MAP_OUT.get(id).get();
+ } else {
+ throw new IllegalArgumentException("This config id doesn't exist: " + id);
+ }
+ }
+
+}
diff --git a/src/main/java/com/glodblock/github/util/FCUtil.java b/src/main/java/com/glodblock/github/util/FCUtil.java
new file mode 100644
index 000000000..084b28733
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/FCUtil.java
@@ -0,0 +1,183 @@
+package com.glodblock.github.util;
+
+import appeng.api.storage.IStorageChannel;
+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.core.Api;
+import appeng.fluids.util.AEFluidInventory;
+import appeng.fluids.util.AEFluidStack;
+import appeng.util.item.AEItemStack;
+import com.glodblock.github.FluidCraft;
+import io.netty.buffer.ByteBuf;
+import io.netty.handler.codec.EncoderException;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenCustomHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
+import net.minecraft.block.Block;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.tileentity.TileEntityType;
+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.items.IItemHandlerModifiable;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+public final class FCUtil {
+
+ private static final Object2ReferenceMap, TileEntityType extends TileEntity>> TILE_CACHE = new Object2ReferenceOpenCustomHashMap<>(HashUtil.CLASS);
+ public static final IStorageChannel ITEM = Api.instance().storage().getStorageChannel(IItemStorageChannel.class);
+ public static final IStorageChannel FLUID = Api.instance().storage().getStorageChannel(IFluidStorageChannel.class);
+
+ @Nonnull
+ public static FluidStack getFluidFromItem(ItemStack stack) {
+ if (!stack.isEmpty() && stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null).resolve().isPresent()) {
+ IFluidHandlerItem tanks = stack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null).resolve().get();
+ for (int i = 0; i < tanks.getTanks(); i ++) {
+ FluidStack fluid = tanks.getFluidInTank(i);
+ if (!fluid.isEmpty()) {
+ return fluid.copy();
+ }
+ }
+ }
+ return FluidStack.EMPTY;
+ }
+
+ public static void writeFluidInventoryToBuffer(@Nonnull AEFluidInventory inv, PacketBuffer data) {
+ int fluidMask = 0;
+ for (int i = 0; i < inv.getSlots(); i++) {
+ if (inv.getFluidInSlot(i) != null) {
+ fluidMask |= 1 << i;
+ }
+ }
+ data.writeByte(fluidMask);
+ for (int i = 0; i < inv.getSlots(); i++) {
+ IAEFluidStack fluid = inv.getFluidInSlot(i);
+ if (fluid != null) {
+ fluid.writeToPacket(data);
+ }
+ }
+ }
+
+ public static boolean readFluidInventoryToBuffer(@Nonnull AEFluidInventory inv, PacketBuffer data) {
+ boolean changed = false;
+ int fluidMask = data.readByte();
+ for (int i = 0; i < inv.getSlots(); i++) {
+ if ((fluidMask & (1 << i)) != 0) {
+ IAEFluidStack fluid = AEFluidStack.fromPacket(data);
+ IAEFluidStack origFluid = inv.getFluidInSlot(i);
+ if (!fluid.equals(origFluid) || fluid.getStackSize() != origFluid.getStackSize()) {
+ inv.setFluidInSlot(i, fluid);
+ changed = true;
+ }
+ } else if (inv.getFluidInSlot(i) != null) {
+ inv.setFluidInSlot(i, null);
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ public static void clearItemInventory(IItemHandlerModifiable inv) {
+ for (int i = 0; i < inv.getSlots(); i ++) {
+ inv.setStackInSlot(i, ItemStack.EMPTY);
+ }
+ }
+
+ public static int findMax(Collection list) {
+ int a = Integer.MIN_VALUE;
+ for (int x : list) {
+ a = Math.max(x, a);
+ }
+ return a;
+ }
+
+ public static ItemStack[] compress(ItemStack[] list) {
+ List comp = new LinkedList<>();
+ for (ItemStack item : list) {
+ if (item == null) continue;
+ ItemStack currentStack = item.copy();
+ if (currentStack.isEmpty() || currentStack.getCount() == 0) continue;
+ boolean find = false;
+ for (ItemStack storedStack : comp) {
+ if (storedStack.isEmpty()) continue;
+ boolean areItemStackEqual = storedStack.isItemEqual(currentStack) && ItemStack.areItemStackTagsEqual(storedStack, currentStack);
+ if (areItemStackEqual && (storedStack.getCount() + currentStack.getCount()) <= storedStack.getMaxStackSize()) {
+ find = true;
+ storedStack.setCount(storedStack.getCount() + currentStack.getCount());
+ }
+ }
+ if (!find) {
+ comp.add(item.copy());
+ }
+ }
+ return comp.stream().filter(Objects::nonNull).toArray(ItemStack[]::new);
+ }
+
+ public static void fuzzyTransferItems(int slot, ItemStack[] inputs, ItemStack[] des, IItemList storage) {
+ if (slot < des.length && inputs.length > 0) {
+ if (storage != null) {
+ IAEItemStack select = AEItemStack.fromItemStack(inputs[0]);
+ for (ItemStack item : inputs) {
+ IAEItemStack result = storage.findPrecise(AEItemStack.fromItemStack(item));
+ if (result != null) {
+ select = AEItemStack.fromItemStack(item);
+ break;
+ }
+ }
+ if (select != null) {
+ des[slot] = select.createItemStack();
+ }
+ } else {
+ des[slot] = inputs[0];
+ }
+ }
+ }
+
+ @SuppressWarnings("all")
+ public static TileEntityType getTileType(Class clazz, Block block) {
+ if (block == null) {
+ return (TileEntityType) TILE_CACHE.get(clazz);
+ }
+ return (TileEntityType) TILE_CACHE.computeIfAbsent(
+ clazz,
+ k -> TileEntityType.Builder.create(
+ () -> {
+ try {
+ return clazz.newInstance();
+ } catch (InstantiationException|IllegalAccessException e) {
+ FluidCraft.log.error("Fail to bulid TileEntityType: " + clazz.getName());
+ e.printStackTrace();
+ return null;
+ }
+ }, block).build(null)
+ );
+ }
+
+ public static void writeNBTToBytes(ByteBuf buf, CompoundNBT nbt) {
+ PacketBuffer pb = new PacketBuffer(buf);
+ try {
+ pb.writeCompoundTag(nbt);
+ } catch (EncoderException ignore) {
+ }
+ }
+
+ public static CompoundNBT readNBTFromBytes(ByteBuf from)
+ {
+ PacketBuffer pb = new PacketBuffer(from);
+ return pb.readCompoundTag();
+ }
+
+}
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..e1d551abe
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/FluidPatternDetails.java
@@ -0,0 +1,152 @@
+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.CraftingInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.nbt.ListNBT;
+import net.minecraft.world.World;
+
+import java.util.*;
+
+public class FluidPatternDetails implements ICraftingPatternDetails, Comparable {
+
+ private final ItemStack patternStack;
+ private final IAEItemStack patternStackAe;
+ private final IAEItemStack[] inputs;
+ private final IAEItemStack[] outputs;
+ private final List inputsCond;
+ private final List outputsCond;
+ private int priority = 0;
+
+ private FluidPatternDetails(ItemStack pattern) {
+ CompoundNBT encodedValue = pattern.getTag();
+ this.patternStack = pattern;
+ this.patternStackAe = Objects.requireNonNull(AEItemStack.fromItemStack(pattern)); // s2g
+ if (encodedValue == null) {
+ throw new IllegalArgumentException("No pattern here!");
+ } else {
+ ListNBT inTag = encodedValue.getList("in", 10);
+ ListNBT outTag = encodedValue.getList("out", 10);
+ this.inputs = new IAEItemStack[inTag.size()];
+ this.outputs = new IAEItemStack[outTag.size()];
+ for(int x = 0; x < inTag.size(); x++) {
+ CompoundNBT ingredient = inTag.getCompound(x);
+ if (!ingredient.isEmpty()) {
+ IAEItemStack stack = AEItemStack.fromNBT(ingredient);
+ this.inputs[x] = stack;
+ }
+ }
+ for(int x = 0; x < outTag.size(); x++) {
+ CompoundNBT ingredient = outTag.getCompound(x);
+ if (!ingredient.isEmpty()) {
+ IAEItemStack stack = AEItemStack.fromNBT(ingredient);
+ this.outputs[x] = stack;
+ }
+ }
+ this.inputsCond = condenseStacks(inputs);
+ this.outputsCond = condenseStacks(outputs);
+ }
+ }
+
+ public static FluidPatternDetails fromPattern(ItemStack pattern) {
+ try {
+ return new FluidPatternDetails(pattern);
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ @Override
+ public ItemStack getPattern() {
+ return patternStack;
+ }
+
+ @Override
+ public boolean isValidItemForSlot(int i, ItemStack itemStack, World world) {
+ throw new IllegalStateException("Not a crafting recipe!");
+ }
+
+ @Override
+ public int getPriority() {
+ return priority;
+ }
+
+ @Override
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ @Override
+ public boolean isCraftable() {
+ return false;
+ }
+
+ @Override
+ public List getInputs() {
+ return this.inputsCond;
+ }
+
+ @Override
+ public List getOutputs() {
+ return this.outputsCond;
+ }
+
+ @Override
+ public IAEItemStack[] getSparseInputs() {
+ return this.inputs;
+ }
+
+ @Override
+ public IAEItemStack[] getSparseOutputs() {
+ return this.outputs;
+ }
+
+ @Override
+ public boolean canSubstitute() {
+ return false;
+ }
+
+ @Override
+ public List getSubstituteInputs(int i) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public ItemStack getOutput(CraftingInventory craftingInventory, World world) {
+ throw new IllegalStateException("Not a crafting recipe!");
+ }
+
+ public static List condenseStacks(IAEItemStack[] stacks) {
+ 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 new ArrayList<>(accMap.values());
+ }
+
+ @Override
+ public int hashCode() {
+ return patternStackAe.hashCode();
+ }
+
+ @Override
+ public int compareTo(ICraftingPatternDetails o) {
+ return Integer.compare(o.getPriority(), this.priority);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof FluidPatternDetails && patternStackAe.equals(((FluidPatternDetails)obj).patternStackAe);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/glodblock/github/util/FluidRenderUtils.java b/src/main/java/com/glodblock/github/util/FluidRenderUtils.java
new file mode 100644
index 000000000..d76913571
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/FluidRenderUtils.java
@@ -0,0 +1,124 @@
+package com.glodblock.github.util;
+
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.client.gui.me.common.StackSizeRenderer;
+import com.glodblock.github.common.item.ItemFluidDrop;
+import com.glodblock.github.common.item.ItemFluidPacket;
+import com.mojang.blaze3d.platform.GlStateManager;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.fluid.Fluid;
+import net.minecraft.fluid.Fluids;
+import net.minecraft.inventory.container.PlayerContainer;
+import net.minecraft.inventory.container.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.FluidStack;
+import org.lwjgl.opengl.GL11;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+@SuppressWarnings("deprecation")
+public class FluidRenderUtils {
+
+ @Nullable
+ public static TextureAtlasSprite prepareRender(@Nullable Fluid fluid) {
+ if (fluid == null || fluid == Fluids.EMPTY) {
+ return null;
+ }
+ TextureAtlasSprite sprite = Minecraft.getInstance().getAtlasSpriteGetter(PlayerContainer.LOCATION_BLOCKS_TEXTURE)
+ .apply(fluid.getAttributes().getStillTexture());
+ int colour = fluid.getAttributes().getColor();
+ GlStateManager.color4f(
+ ((colour >> 16) & 0xFF) / 255F,
+ ((colour >> 8) & 0xFF) / 255F,
+ (colour & 0xFF) / 255F,
+ ((colour >> 24) & 0xFF) / 255F);
+ return sprite;
+ }
+
+ @Nullable
+ public static TextureAtlasSprite prepareRender(@Nonnull FluidStack fluidStack) {
+ if (!fluidStack.isEmpty()) {
+ return prepareRender(fluidStack.getFluid());
+ }
+ return null;
+ }
+
+ 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.param,
+ GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA.param
+ );
+ 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);
+ float y1 = y + height - fluidHeight, y2 = y1 + Math.min(fluidHeight, width);
+ float u1 = sprite.getMinU(), v1 = sprite.getMinV(), u2 = sprite.getMaxU(), v2 = sprite.getMaxV();
+ if (fluidHeight < width) {
+ v2 = v1 + (v2 - v1) * (fluidHeight / (float)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 renderFluidIntoGuiCleanly(int x, int y, int width, int height,
+ @Nonnull FluidStack fluidStack, int capacity) {
+ Minecraft.getInstance().getTextureManager().bindTexture(PlayerContainer.LOCATION_BLOCKS_TEXTURE);
+ Tessellator tess = Tessellator.getInstance();
+ renderFluidIntoGui(tess, tess.getBuffer(), x, y, width, height, fluidStack, capacity);
+ GlStateManager.color4f(1F, 1F, 1F, 1F);
+ }
+
+ public static boolean renderFluidIntoGuiSlot(Slot slot, @Nonnull FluidStack fluid,
+ StackSizeRenderer stackSizeRenderer, FontRenderer fontRenderer) {
+ if (fluid.isEmpty()) {
+ return false;
+ }
+ renderFluidIntoGuiCleanly(slot.xPos, slot.yPos, 16, 16, fluid, fluid.getAmount());
+ stackSizeRenderer.renderStackSize(fontRenderer, ItemFluidDrop.newAeStack(fluid).getStackSize(), false, slot.xPos, slot.yPos);
+ return true;
+ }
+
+ 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,
+ @Nonnull FluidStack fluidStack, int capacity) {
+ if (!fluidStack.isEmpty()) {
+ TextureAtlasSprite sprite = FluidRenderUtils.prepareRender(fluidStack);
+ if (sprite != null) {
+ doRenderFluid(tess, buf, x, y, width, height, sprite, fluidStack.getAmount() / (double)capacity);
+ }
+ }
+ }
+
+ 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/util/HashUtil.java b/src/main/java/com/glodblock/github/util/HashUtil.java
new file mode 100644
index 000000000..506b28bcf
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/HashUtil.java
@@ -0,0 +1,35 @@
+package com.glodblock.github.util;
+
+import com.mojang.datafixers.util.Pair;
+import it.unimi.dsi.fastutil.Hash;
+import net.minecraft.fluid.Fluid;
+
+import java.util.Objects;
+
+public class HashUtil {
+
+ public static final Hash.Strategy FLUID = new Hash.Strategy() {
+ @Override
+ public int hashCode(Fluid o) {
+ return Objects.requireNonNull(o.getRegistryName()).hashCode();
+ }
+
+ @Override
+ public boolean equals(Fluid a, Fluid b) {
+ return a == b || (a != null && b != null && Objects.equals(a.getRegistryName(), b.getRegistryName()));
+ }
+ };
+
+ public static final Hash.Strategy> CLASS = new Hash.Strategy>() {
+ @Override
+ public int hashCode(Class> o) {
+ return o.getName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Class> a, Class> b) {
+ return a == b || (a != null && b != null && Objects.equals(a.getName(), b.getName()));
+ }
+ };
+
+}
diff --git a/src/main/java/com/glodblock/github/util/InvalidFCPatternHelper.java b/src/main/java/com/glodblock/github/util/InvalidFCPatternHelper.java
new file mode 100644
index 000000000..1bd4546d9
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/InvalidFCPatternHelper.java
@@ -0,0 +1,92 @@
+package com.glodblock.github.util;
+
+import appeng.api.storage.data.IAEItemStack;
+import appeng.util.Platform;
+import appeng.util.item.AEItemStack;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundNBT;
+import net.minecraft.nbt.ListNBT;
+import net.minecraft.util.text.IFormattableTextComponent;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.StringTextComponent;
+import net.minecraft.util.text.TextFormatting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class InvalidFCPatternHelper {
+ private final List outputs = new ArrayList<>();
+ private final List inputs = new ArrayList<>();
+
+ public InvalidFCPatternHelper(ItemStack is) {
+ CompoundNBT encodedValue = is.getTag();
+ if (encodedValue == null) {
+ throw new IllegalArgumentException("No pattern here!");
+ } else {
+ ListNBT inTag = encodedValue.getList("in", 10);
+ ListNBT outTag = encodedValue.getList("out", 10);
+
+ int i;
+ for(i = 0; i < outTag.size(); ++i) {
+ this.outputs.add(new PatternIngredient(outTag.getCompound(i)));
+ }
+
+ for(i = 0; i < inTag.size(); ++i) {
+ CompoundNBT in = inTag.getCompound(i);
+ if (!in.isEmpty()) {
+ this.inputs.add(new PatternIngredient(in));
+ }
+ }
+ }
+ }
+
+ public List getOutputs() {
+ return this.outputs;
+ }
+
+ public List getInputs() {
+ return this.inputs;
+ }
+
+ public static class PatternIngredient {
+
+ private final IAEItemStack stack;
+ private String id;
+ private int count;
+ private int damage;
+
+ public PatternIngredient(CompoundNBT tag) {
+ this.stack = AEItemStack.fromNBT(tag);
+ if (this.isValid()) {
+ CompoundNBT is = tag.getCompound("is");
+ this.id = is.getString("id");
+ this.count = (int) this.stack.getStackSize();
+ this.damage = Math.max(0, tag.getShort("Damage"));
+ }
+ }
+
+ public boolean isValid() {
+ return this.stack != null;
+ }
+
+ public ITextComponent getName() {
+ return this.isValid() ? Platform.getItemDisplayName(this.stack) : new StringTextComponent(this.id + '@' + this.getDamage());
+ }
+
+ public int getDamage() {
+ return this.isValid() ? this.stack.getItemDamage() : this.damage;
+ }
+
+ public int getCount() {
+ return this.isValid() ? (int) this.stack.getStackSize() : this.count;
+ }
+
+ public ITextComponent getFormattedToolTip() {
+ IFormattableTextComponent result = (new StringTextComponent(this.getCount() + " ")).appendSibling(this.getName());
+ if (!this.isValid()) {
+ result.mergeStyle(TextFormatting.RED);
+ }
+ return result;
+ }
+ }
+}
\ 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..a01f06d08
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/ModAndClassUtil.java
@@ -0,0 +1,30 @@
+package com.glodblock.github.util;
+
+import net.minecraftforge.fml.ModList;
+
+public final class ModAndClassUtil {
+
+ public static boolean AUTO_P = false;
+ public static boolean NEE = false;
+ public static boolean JEI = false;
+ public static boolean REI = false;
+
+ public static void init() {
+ if (ModList.get().isLoaded("packagedauto")) {
+ AUTO_P = true;
+ }
+
+ if (ModList.get().isLoaded("neenergistics")) {
+ NEE = true;
+ }
+
+ if (ModList.get().isLoaded("jei")) {
+ JEI = true;
+ }
+
+ if (ModList.get().isLoaded("roughlyenoughitems")) {
+ REI = 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..ee38c8310
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/MouseRegionManager.java
@@ -0,0 +1,84 @@
+package com.glodblock.github.util;
+
+import appeng.client.gui.AEBaseScreen;
+import com.mojang.blaze3d.matrix.MatrixStack;
+import net.minecraft.client.audio.SimpleSound;
+import net.minecraft.util.SoundEvents;
+import net.minecraft.util.text.ITextComponent;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MouseRegionManager {
+
+ private final AEBaseScreen> gui;
+ private final List regions = new ArrayList<>();
+
+ public MouseRegionManager(AEBaseScreen> 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(double mX, double mY, int button) {
+ mX -= gui.getGuiLeft();
+ mY -= gui.getGuiTop();
+ for (Region region : regions) {
+ if (region.containsMouse(mX, mY) && region.handler.onClick(button)) {
+ gui.getMinecraft().getSoundHandler().play(SimpleSound.master(SoundEvents.UI_BUTTON_CLICK, 1F));
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void render(MatrixStack mStack, 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.drawTooltip(mStack, mX, mY, tooltip);
+ 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(double mX, double 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..ec4d56110
--- /dev/null
+++ b/src/main/java/com/glodblock/github/util/NameConst.java
@@ -0,0 +1,57 @@
+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_LARGE_INGREDIENT_BUFFER = "large_ingredient_buffer";
+ public static final String BLOCK_BURETTE = "burette";
+ public static final String BLOCK_DUAL_INTERFACE = "dual_interface";
+ public static final String BLOCK_FLUID_LEVEL_MAINTAINER = "fluid_level_maintainer";
+ public static final String BLOCK_FLUID_ASSEMBLER = "fluid_assembler";
+
+ 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 = "fluid_encoded_pattern";
+ public static final String ITEM_DENSE_CRAFT_ENCODED_PATTERN = "dense_craft_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 ITEM_PART_EXTENDED_FLUID_PATTERN_TERMINAL = "part_fluid_pattern_ex_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_FLUID_PACKET_INFO = TT_KEY + "fluid_packet_info";
+ 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_LARGE_INGREDIENT_BUFFER = GUI_KEY + BLOCK_LARGE_INGREDIENT_BUFFER;
+ public static final String GUI_BURETTE = GUI_KEY + BLOCK_BURETTE;
+ public static final String GUI_FLUID_LEVEL_MAINTAINER = GUI_KEY + BLOCK_FLUID_LEVEL_MAINTAINER;
+ public static final String GUI_FLUID_ASSEMBLER = GUI_KEY + BLOCK_FLUID_ASSEMBLER;
+ public static final String GUI_ITEM_AMOUNT_SET = GUI_KEY + "item_amount_set";
+ public static final String GUI_ITEM_AMOUNT_SET_CONFIRM = GUI_KEY + "set";
+
+ public static final String MISC = FluidCraft.MODID + ".misc.";
+ public static final String MISC_THRESHOLD = MISC + "threshold";
+ public static final String MISC_REQ = MISC + "request";
+
+ public static final ResourceLocation MODEL_DENSE_ENCODED_PATTERN = FluidCraft.resource("builtin/fluid_encoded_pattern");
+ public static final ResourceLocation MODEL_DENSE_CRAFT_ENCODED_PATTERN = FluidCraft.resource("builtin/dense_craft_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/resources/META-INF/ae2fc_at.cfg b/src/main/resources/META-INF/ae2fc_at.cfg
new file mode 100644
index 000000000..f1c62c4f0
--- /dev/null
+++ b/src/main/resources/META-INF/ae2fc_at.cfg
@@ -0,0 +1 @@
+protected net.minecraft.inventory.container.Container field_75149_d # listeners
diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml
new file mode 100644
index 000000000..577f33a54
--- /dev/null
+++ b/src/main/resources/META-INF/mods.toml
@@ -0,0 +1,48 @@
+modLoader="javafml"
+loaderVersion="[32,)"
+issueTrackerURL="https://github.com/GlodBlock/AE2FluidCraft-Rework/issues"
+displayURL="https://github.com/GlodBlock/AE2FluidCraft-Rework"
+logoFile="logo.png"
+authors="GlodBlock"
+license="LGPL-3.0"
+
+[[mods]]
+modId="ae2fc"
+version="1.0.0-a"
+displayName="Fluid Craft for AE2"
+description="Lets you do Crafting... with Fluids!"
+
+[[dependencies.ae2fc]]
+modId="forge"
+mandatory=true
+versionRange="[36.1.10,37.0.0)"
+ordering="NONE"
+side="BOTH"
+
+[[dependencies.ae2fc]]
+modId="minecraft"
+mandatory=true
+versionRange="[1.16.5,1.17.0)"
+ordering="NONE"
+side="BOTH"
+
+[[dependencies.ae2fc]]
+modId="jei"
+mandatory=false
+versionRange="[7.7.0.98,)"
+ordering="AFTER"
+side="CLIENT"
+
+[[dependencies.ae2fc]]
+modId="roughlyenoughitems"
+mandatory=false
+versionRange="[6.5.436,)"
+ordering="AFTER"
+side="CLIENT"
+
+[[dependencies.ae2fc]]
+modId="appliedenergistics2"
+mandatory=true
+versionRange="[8.4.7,)"
+ordering="AFTER"
+side="BOTH"
\ No newline at end of file
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..399a22d6c
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/dual_interface.json
@@ -0,0 +1,48 @@
+{
+ "variants": {
+ "omnidirectional=true,facing=east": {
+ "model": "ae2fc:block/dual_interface"
+ },
+ "omnidirectional=true,facing=west": {
+ "model": "ae2fc:block/dual_interface"
+ },
+ "omnidirectional=true,facing=south": {
+ "model": "ae2fc:block/dual_interface"
+ },
+ "omnidirectional=true,facing=north": {
+ "model": "ae2fc:block/dual_interface"
+ },
+ "omnidirectional=true,facing=up": {
+ "model": "ae2fc:block/dual_interface"
+ },
+ "omnidirectional=true,facing=down": {
+ "model": "ae2fc:block/dual_interface"
+ },
+ "omnidirectional=false,facing=east": {
+ "model": "ae2fc:block/dual_interface_oriented",
+ "x": 90,
+ "y": 90
+ },
+ "omnidirectional=false,facing=west": {
+ "model": "ae2fc:block/dual_interface_oriented",
+ "x": 90,
+ "y": 270
+ },
+ "omnidirectional=false,facing=south": {
+ "model": "ae2fc:block/dual_interface_oriented",
+ "x": 270
+ },
+ "omnidirectional=false,facing=north": {
+ "model": "ae2fc:block/dual_interface_oriented",
+ "x": 90
+ },
+ "omnidirectional=false,facing=up": {
+ "model": "ae2fc:block/dual_interface_oriented",
+ "x": 0
+ },
+ "omnidirectional=false,facing=down": {
+ "model": "ae2fc:block/dual_interface_oriented",
+ "x": 180
+ }
+ }
+}
\ No newline at end of file
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..819b26baf
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/fluid_discretizer.json
@@ -0,0 +1,7 @@
+{
+ "variants": {
+ "": {
+ "model": "ae2fc:block/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..0205c7610
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/fluid_packet_decoder.json
@@ -0,0 +1,7 @@
+{
+ "variants": {
+ "": {
+ "model": "ae2fc:block/fluid_packet_decoder"
+ }
+ }
+}
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..a48586a74
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/ingredient_buffer.json
@@ -0,0 +1,7 @@
+{
+ "variants": {
+ "": {
+ "model": "ae2fc:block/ingredient_buffer"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/blockstates/large_ingredient_buffer.json b/src/main/resources/assets/ae2fc/blockstates/large_ingredient_buffer.json
new file mode 100644
index 000000000..980e8487b
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/blockstates/large_ingredient_buffer.json
@@ -0,0 +1,7 @@
+{
+ "variants": {
+ "": {
+ "model": "ae2fc:block/large_ingredient_buffer"
+ }
+ }
+}
diff --git a/src/main/resources/assets/ae2fc/lang/en_us.json b/src/main/resources/assets/ae2fc/lang/en_us.json
new file mode 100644
index 000000000..07b1e79be
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/lang/en_us.json
@@ -0,0 +1,57 @@
+{
+ "itemGroup.ae2fc": "AE2 Fluid Crafting",
+
+ "block.ae2fc.ingredient_buffer": "Ingredient Buffer",
+ "block.ae2fc.large_ingredient_buffer": "Large Ingredient Buffer",
+ "block.ae2fc.fluid_discretizer": "ME Fluid Discretizer",
+ "block.ae2fc.dual_interface": "ME Dual Interface",
+ "block.ae2fc.fluid_packet_decoder": "ME Fluid Packet Decoder",
+
+ "item.ae2fc.fluid_drop": "Drop of %s",
+ "item.ae2fc.fluid_packet": "Fluid Packet",
+ "item.ae2fc.part_fluid_pattern_terminal": "ME Fluid Pattern Terminal",
+ "item.ae2fc.part_dual_interface": "ME Dual Interface",
+ "item.ae2fc.fluid_encoded_pattern": "Encoded Pattern",
+
+ "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.large_ingredient_buffer": "Large Ingredient Buffer",
+ "ae2fc.gui.burette": "Precision Burette",
+ "ae2fc.gui.fluid_level_maintainer": "ME Fluid Level Maintainer",
+ "ae2fc.gui.fluid_assembler": "ME Fluid Assembler",
+ "ae2fc.gui.fluid_pattern_terminal": "Fluid Pattern Terminal",
+ "ae2fc.gui.item_amount_set": "Set Amount",
+ "ae2fc.gui.set": "Set",
+
+ "ae2fc.tooltip.empty": "Empty",
+ "ae2fc.tooltip.dump_tank": "Dump Tank Contents",
+ "ae2fc.tooltip.invalid_fluid": "Invalid fluid!",
+ "ae2fc.tooltip.fluid_packet": "Use a Fluid Packet Decoder to convert this back into fluid.",
+ "ae2fc.tooltip.fluid_packet_info": "%s, %s mB",
+
+ "ae2fc.tooltip.not_combine": "Merge the same items",
+ "ae2fc.tooltip.not_combine.hint": "Disabled",
+ "ae2fc.tooltip.combine": "Merge the same items",
+ "ae2fc.tooltip.combine.hint": "Enabled",
+ "ae2fc.tooltip.send_fluid": "Send Real Fluid",
+ "ae2fc.tooltip.send_fluid.hint": "The real fluid will be sent to target container",
+ "ae2fc.tooltip.send_packet": "Send Fluid Packet",
+ "ae2fc.tooltip.send_packet.hint": "The fluid packet item will be sent to target container",
+ "ae2fc.tooltip.fluid_first": "Place Fluid First In Pattern",
+ "ae2fc.tooltip.fluid_first.hint": "Enable",
+ "ae2fc.tooltip.item_first": "Place Fluid First In Pattern",
+ "ae2fc.tooltip.item_first.hint": "Disabled",
+ "ae2fc.tooltip.craft_fluid": "Encode Pattern",
+ "ae2fc.tooltip.craft_fluid.hint": "Special pattern for ME fluid assembler. You can use fluid directly in crafting recipes instead of buckets.",
+ "ae2fc.tooltip.splitting": "Allow Splitting Items and Fluids",
+ "ae2fc.tooltip.splitting.hint": "Items and fluids may be separated to different sides if the interface does not point into any direction.",
+ "ae2fc.tooltip.not_splitting": "Prevent Splitting Items and Fluids",
+ "ae2fc.tooltip.not_splitting.hint": "Items and fluids can only be pushed out together through the same side of the interface.",
+ "ae2fc.tooltip.block_all": "Block All",
+ "ae2fc.tooltip.block_all.hint": "Blocking mode checks both item and fluid.",
+ "ae2fc.tooltip.block_item": "Block Item",
+ "ae2fc.tooltip.block_item.hint": "Blocking mode only checks item.",
+ "ae2fc.tooltip.block_fluid": "Block Fluid",
+ "ae2fc.tooltip.block_fluid.hint": "Blocking mode only checks fluid."
+}
\ No newline at end of file
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_assembler.json b/src/main/resources/assets/ae2fc/models/block/fluid_assembler.json
new file mode 100644
index 000000000..9d17496c4
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/fluid_assembler.json
@@ -0,0 +1,6 @@
+{
+ "parent": "block/cube_all",
+ "textures": {
+ "all": "ae2fc:blocks/fluid_assembler"
+ }
+}
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_level_maintainer.json b/src/main/resources/assets/ae2fc/models/block/fluid_level_maintainer.json
new file mode 100644
index 000000000..b4de2a3f1
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/fluid_level_maintainer.json
@@ -0,0 +1,12 @@
+{
+ "parent": "block/orientable",
+ "textures": {
+ "particle": "ae2fc:blocks/fluid_level_maintainer",
+ "down": "ae2fc:blocks/fluid_level_maintainer_side",
+ "up": "ae2fc:blocks/fluid_level_maintainer",
+ "north": "ae2fc:blocks/fluid_level_maintainer_side",
+ "south": "ae2fc:blocks/fluid_level_maintainer_side",
+ "east": "ae2fc:blocks/fluid_level_maintainer_side",
+ "west": "ae2fc:blocks/fluid_level_maintainer_side"
+ }
+}
\ No newline at end of file
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/block/large_ingredient_buffer.json b/src/main/resources/assets/ae2fc/models/block/large_ingredient_buffer.json
new file mode 100644
index 000000000..3c79f2d95
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/block/large_ingredient_buffer.json
@@ -0,0 +1,6 @@
+{
+ "parent": "block/cube_all",
+ "textures": {
+ "all": "ae2fc:blocks/large_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_craft_encoded_pattern.json b/src/main/resources/assets/ae2fc/models/item/dense_craft_encoded_pattern.json
new file mode 100644
index 000000000..b0c5dcfd1
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/dense_craft_encoded_pattern.json
@@ -0,0 +1,6 @@
+{
+ "parent": "item/generated",
+ "textures": {
+ "layer0": "ae2fc:items/dense_craft_encoded_pattern"
+ }
+}
\ No newline at end of file
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_assembler.json b/src/main/resources/assets/ae2fc/models/item/fluid_assembler.json
new file mode 100644
index 000000000..33b212cbf
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_assembler.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2fc:block/fluid_assembler"
+}
\ No newline at end of file
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_encoded_pattern.json b/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern.json
new file mode 100644
index 000000000..a31fec85b
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern.json
@@ -0,0 +1,4 @@
+{
+ "loader": "ae2fc:fluid_encoded_pattern",
+ "baseModel": "ae2fc:item/fluid_encoded_pattern_base"
+}
diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern_base.json b/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern_base.json
new file mode 100644
index 000000000..dcf8ebad7
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_encoded_pattern_base.json
@@ -0,0 +1,6 @@
+{
+ "parent": "item/generated",
+ "textures": {
+ "layer0": "ae2fc:items/fluid_encoded_pattern"
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/ae2fc/models/item/fluid_level_maintainer.json b/src/main/resources/assets/ae2fc/models/item/fluid_level_maintainer.json
new file mode 100644
index 000000000..95e97f173
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_level_maintainer.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2fc:block/fluid_level_maintainer"
+}
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..405e2874e
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/fluid_packet.json
@@ -0,0 +1,8 @@
+{
+ "loader": "ae2fc:fluid_packet",
+ "parent": "item/generated",
+ "textures": {
+ "layer0": "ae2fc:items/fluid_packet_mask",
+ "layer1": "ae2fc:items/fluid_packet"
+ }
+}
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/large_ingredient_buffer.json b/src/main/resources/assets/ae2fc/models/item/large_ingredient_buffer.json
new file mode 100644
index 000000000..d324c988b
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/large_ingredient_buffer.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2fc:block/large_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..be9fa71be
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/part_dual_interface.json
@@ -0,0 +1,106 @@
+{
+ "parent": "appliedenergistics2:item/part_base",
+ "textures": {
+ "sides": "appliedenergistics2:part/monitor_sides",
+ "back": "appliedenergistics2:part/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_ex_terminal.json b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_ex_terminal.json
new file mode 100644
index 000000000..dced00638
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_ex_terminal.json
@@ -0,0 +1,9 @@
+{
+ "parent": "appliedenergistics2:item/part/display",
+ "textures": {
+ "front": "appliedenergistics2:items/part/expanded_processing_pattern_terminal",
+ "front_bright": "ae2fc:parts/pattern_terminal_ex_bright",
+ "front_medium": "ae2fc:parts/pattern_terminal_ex_medium",
+ "front_dark": "ae2fc:parts/pattern_terminal_ex_dark"
+ }
+}
\ No newline at end of file
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..6c08834b5
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/item/part_fluid_pattern_terminal.json
@@ -0,0 +1,9 @@
+{
+ "parent": "appliedenergistics2:item/display_base",
+ "textures": {
+ "front": "appliedenergistics2:part/pattern_terminal",
+ "front_bright": "ae2fc:parts/pattern_terminal_bright",
+ "front_medium": "ae2fc:parts/pattern_terminal_medium",
+ "front_dark": "ae2fc:parts/pattern_terminal_dark"
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_off.json b/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_off.json
new file mode 100644
index 000000000..64da5c376
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_off.json
@@ -0,0 +1,8 @@
+{
+ "parent": "appliedenergistics2:part/display_off",
+ "textures": {
+ "lightsBright": "ae2fc:parts/pattern_terminal_ex_bright",
+ "lightsMedium": "ae2fc:parts/pattern_terminal_ex_medium",
+ "lightsDark": "ae2fc:parts/pattern_terminal_ex_dark"
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_on.json b/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_on.json
new file mode 100644
index 000000000..7f91dad83
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_ex_term_on.json
@@ -0,0 +1,76 @@
+{
+ "ae2_uvl_marker": true,
+ "textures": {
+ "lightsBright": "ae2fc:parts/pattern_terminal_ex_bright",
+ "lightsMedium": "ae2fc:parts/pattern_terminal_ex_medium",
+ "lightsDark": "ae2fc:parts/pattern_terminal_ex_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
+ }
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
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..f23c08f8b
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/f_pattern_term_on.json
@@ -0,0 +1,8 @@
+{
+ "parent": "appliedenergistics2:part/pattern_terminal_on",
+ "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/interface_base.json b/src/main/resources/assets/ae2fc/models/part/interface_base.json
new file mode 100644
index 000000000..c3eb02ab5
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/interface_base.json
@@ -0,0 +1,104 @@
+{
+ "textures": {
+ "sides": "appliedenergistics2:part/export_bus_sides",
+ "sidesStatus": "appliedenergistics2:part/monitor_sides_status",
+ "back": "appliedenergistics2:part/monitor_back",
+ "front": "ae2fc:blocks/interface",
+ "particle": "appliedenergistics2:part/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..77bbb93c2
--- /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:part/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..433a4a233
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/interface_off.json
@@ -0,0 +1,33 @@
+{
+ "textures": {
+ "indicator": "appliedenergistics2:part/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..187259ceb
--- /dev/null
+++ b/src/main/resources/assets/ae2fc/models/part/interface_on.json
@@ -0,0 +1,54 @@
+{
+ "ae2_uvl_marker": true,
+ "textures": {
+ "indicator": "appliedenergistics2:part/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/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_assembler.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_assembler.png
new file mode 100644
index 000000000..a0859beb0
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/fluid_assembler.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..c0da91e8e
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_level_maintainer.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_level_maintainer.png
new file mode 100644
index 000000000..66f1159ee
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/fluid_level_maintainer.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/blocks/fluid_level_maintainer_side.png b/src/main/resources/assets/ae2fc/textures/blocks/fluid_level_maintainer_side.png
new file mode 100644
index 000000000..88304b937
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/fluid_level_maintainer_side.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..a6c298937
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..99ce20c0e
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..80a60336b
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/blocks/large_ingredient_buffer.png b/src/main/resources/assets/ae2fc/textures/blocks/large_ingredient_buffer.png
new file mode 100644
index 000000000..31d8cd0b8
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/blocks/large_ingredient_buffer.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/items/dense_craft_encoded_pattern.png b/src/main/resources/assets/ae2fc/textures/items/dense_craft_encoded_pattern.png
new file mode 100644
index 000000000..86bc00a2f
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/items/dense_craft_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_encoded_pattern.png b/src/main/resources/assets/ae2fc/textures/items/fluid_encoded_pattern.png
new file mode 100644
index 000000000..f4210af1e
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/items/fluid_encoded_pattern.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..9b745b1ec
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_mask.png b/src/main/resources/assets/ae2fc/textures/items/fluid_packet_mask.png
new file mode 100644
index 000000000..0ed80022c
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/items/fluid_packet_mask.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_ex_bright.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_bright.png
new file mode 100644
index 000000000..45985f13d
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_bright.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_dark.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_dark.png
new file mode 100644
index 000000000..44e4c64ff
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_dark.png differ
diff --git a/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_medium.png b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_medium.png
new file mode 100644
index 000000000..e881def0c
Binary files /dev/null and b/src/main/resources/assets/ae2fc/textures/parts/pattern_terminal_ex_medium.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/assets/appliedenergistics2/screens/dual_fluid_interface.json b/src/main/resources/assets/appliedenergistics2/screens/dual_fluid_interface.json
new file mode 100644
index 000000000..ddb529421
--- /dev/null
+++ b/src/main/resources/assets/appliedenergistics2/screens/dual_fluid_interface.json
@@ -0,0 +1,90 @@
+{
+ "$schema": "schema.json",
+ "includes": ["common/common.json", "common/player_inventory.json"],
+ "background": {
+ "texture": "guis/interfacefluid.png",
+ "srcRect": [0, 0, 176, 231]
+ },
+ "slots": {
+ "CONFIG": {
+ "left": 35,
+ "top": 35,
+ "grid": "HORIZONTAL"
+ }
+ },
+ "text": {
+ "dialog_title": {
+ "text": {
+ "translate": "gui.appliedenergistics2.FluidInterface"
+ },
+ "position": {
+ "left": 8,
+ "top": 6
+ }
+ },
+ "interface_config": {
+ "text": {
+ "translate": "gui.appliedenergistics2.Config"
+ },
+ "position": {
+ "left": 35,
+ "top": 24
+ }
+ },
+ "interface_stored_fluids": {
+ "text": {
+ "translate": "gui.appliedenergistics2.StoredFluids"
+ },
+ "position": {
+ "left": 35,
+ "top": 125
+ }
+ }
+ },
+ "widgets": {
+ "priorityBtn": {
+ "left": 154,
+ "top": 0
+ },
+ "switchInterface": {
+ "left": 133,
+ "top": 0
+ },
+ "tank1": {
+ "left": 35,
+ "top": 53,
+ "width": 16,
+ "height": 68
+ },
+ "tank2": {
+ "left": 53,
+ "top": 53,
+ "width": 16,
+ "height": 68
+ },
+ "tank3": {
+ "left": 71,
+ "top": 53,
+ "width": 16,
+ "height": 68
+ },
+ "tank4": {
+ "left": 89,
+ "top": 53,
+ "width": 16,
+ "height": 68
+ },
+ "tank5": {
+ "left": 107,
+ "top": 53,
+ "width": 16,
+ "height": 68
+ },
+ "tank6": {
+ "left": 125,
+ "top": 53,
+ "width": 16,
+ "height": 68
+ }
+ }
+}
diff --git a/src/main/resources/assets/appliedenergistics2/screens/dual_item_interface.json b/src/main/resources/assets/appliedenergistics2/screens/dual_item_interface.json
new file mode 100644
index 000000000..245a93d45
--- /dev/null
+++ b/src/main/resources/assets/appliedenergistics2/screens/dual_item_interface.json
@@ -0,0 +1,73 @@
+{
+ "$schema": "schema.json",
+ "includes": ["common/common.json", "common/player_inventory.json"],
+ "background": {
+ "texture": "guis/interface.png",
+ "srcRect": [0, 0, 176, 211]
+ },
+ "slots": {
+ "CONFIG": {
+ "left": 8,
+ "top": 35,
+ "grid": "HORIZONTAL"
+ },
+ "STORAGE": {
+ "left": 8,
+ "top": 53,
+ "grid": "HORIZONTAL"
+ },
+ "ENCODED_PATTERN": {
+ "left": 8,
+ "top": 97,
+ "grid": "HORIZONTAL"
+ }
+ },
+ "text": {
+ "dialog_title": {
+ "text": {
+ "translate": "gui.appliedenergistics2.Interface"
+ },
+ "position": {
+ "left": 8,
+ "top": 6
+ }
+ },
+ "interface_config": {
+ "text": {
+ "translate": "gui.appliedenergistics2.Config"
+ },
+ "position": {
+ "left": 8,
+ "top": 24
+ }
+ },
+ "interface_stored_items": {
+ "text": {
+ "translate": "gui.appliedenergistics2.StoredItems"
+ },
+ "position": {
+ "left": 8,
+ "top": 73
+ }
+ },
+ "interface_patterns": {
+ "text": {
+ "translate": "gui.appliedenergistics2.Patterns"
+ },
+ "position": {
+ "left": 8,
+ "top": 86
+ }
+ }
+ },
+ "widgets": {
+ "priorityBtn": {
+ "left": 154,
+ "top": 0
+ },
+ "switchInterface": {
+ "left": 133,
+ "top": 0
+ }
+ }
+}
diff --git a/src/main/resources/assets/appliedenergistics2/screens/fc_priority.json b/src/main/resources/assets/appliedenergistics2/screens/fc_priority.json
new file mode 100644
index 000000000..7fc761361
--- /dev/null
+++ b/src/main/resources/assets/appliedenergistics2/screens/fc_priority.json
@@ -0,0 +1,49 @@
+{
+ "$schema": "schema.json",
+ "includes": ["common/common.json"],
+ "background": {
+ "texture": "guis/priority.png",
+ "srcRect": [0, 0, 176, 125]
+ },
+ "text": {
+ "dialog_title": {
+ "text": {
+ "translate": "gui.appliedenergistics2.Priority"
+ },
+ "position": {
+ "left": 8,
+ "top": 6
+ }
+ },
+ "priority_insertion_hint": {
+ "text": {
+ "translate": "gui.appliedenergistics2.PriorityInsertionHint"
+ },
+ "position": {
+ "left": 8,
+ "top": 98
+ }
+ },
+ "priority_extraction_hint": {
+ "text": {
+ "translate": "gui.appliedenergistics2.PriorityExtractionHint"
+ },
+ "position": {
+ "left": 8,
+ "top": 110
+ }
+ }
+ },
+ "widgets": {
+ "back": {
+ "left": 154,
+ "top": 0
+ },
+ "priority": {
+ "left": 20,
+ "top": 30,
+ "width": 138,
+ "height": 62
+ }
+ }
+}
diff --git a/src/main/resources/assets/appliedenergistics2/screens/fluid_packet_decoder.json b/src/main/resources/assets/appliedenergistics2/screens/fluid_packet_decoder.json
new file mode 100644
index 000000000..77ef26f24
--- /dev/null
+++ b/src/main/resources/assets/appliedenergistics2/screens/fluid_packet_decoder.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "schema.json",
+ "includes": ["common/common.json", "common/player_inventory.json"],
+ "background": {
+ "texture": "gui/fluid_packet_decoder.png",
+ "srcRect": [0, 0, 176, 166]
+ },
+ "slots": {
+ "STORAGE": {
+ "left": 80,
+ "top": 35,
+ "grid": "HORIZONTAL"
+ }
+ },
+ "text": {
+ "dialog_title": {
+ "text": {
+ "translate": "ae2fc.gui.fluid_packet_decoder"
+ },
+ "position": {
+ "left": 8,
+ "top": 6
+ }
+ }
+ }
+}
diff --git a/src/main/resources/assets/appliedenergistics2/screens/fluid_pattern_terminal.json b/src/main/resources/assets/appliedenergistics2/screens/fluid_pattern_terminal.json
new file mode 100644
index 000000000..052718a7a
--- /dev/null
+++ b/src/main/resources/assets/appliedenergistics2/screens/fluid_pattern_terminal.json
@@ -0,0 +1,104 @@
+{
+ "$schema": "../schema.json",
+ "includes": ["terminals/item_terminal.json"],
+ "slots": {
+ "BLANK_PATTERN": {
+ "left": 147,
+ "bottom": 161
+ },
+ "ENCODED_PATTERN": {
+ "left": 147,
+ "bottom": 118
+ },
+ "CRAFTING_GRID": {
+ "left": 18,
+ "bottom": 156,
+ "grid": "BREAK_AFTER_3COLS"
+ },
+ "CRAFTING_RESULT": {
+ "left": 110,
+ "bottom": 138
+ },
+ "PROCESSING_RESULT": {
+ "left": 110,
+ "bottom": 156,
+ "grid": "VERTICAL"
+ }
+ },
+ "text": {
+ "dialog_title": {
+ "text": {
+ "translate": "gui.appliedenergistics2.Terminal"
+ },
+ "position": {
+ "left": 8,
+ "top": 6
+ }
+ },
+ "crafting_grid_title": {
+ "text": {
+ "translate": "ae2fc.gui.fluid_pattern_terminal"
+ },
+ "position": {
+ "left": 8,
+ "bottom": 175
+ }
+ }
+ },
+ "terminalStyle": {
+ "header": {
+ "texture": "guis/pattern.png",
+ "srcRect": [0, 0, 195, 17]
+ },
+ "firstRow": {
+ "texture": "guis/pattern.png",
+ "srcRect": [0, 17, 195, 18]
+ },
+ "row": {
+ "texture": "guis/pattern.png",
+ "srcRect": [0, 35, 195, 18]
+ },
+ "lastRow": {
+ "texture": "guis/pattern.png",
+ "srcRect": [0, 53, 195, 18]
+ },
+ "bottom": {
+ "texture": "guis/pattern.png",
+ "srcRect": [0, 71, 195, 178]
+ }
+ },
+ "widgets": {
+ "craftingPatternMode": {
+ "left": 173,
+ "bottom": 177
+ },
+ "processingPatternMode": {
+ "left": 173,
+ "bottom": 177
+ },
+ "substitutionsEnabled": {
+ "left": 84,
+ "bottom": 163
+ },
+ "substitutionsDisabled": {
+ "left": 84,
+ "bottom": 163
+ },
+ "clearPattern": {
+ "left": 74,
+ "bottom": 163
+ },
+ "combineBtn": {
+ "left": 84,
+ "bottom": 163
+ },
+ "fluidBtn": {
+ "left": 74,
+ "bottom": 153
+ },
+ "encodePattern": {
+ "left": 147,
+ "bottom": 142
+ }
+ }
+}
diff --git a/src/main/resources/assets/appliedenergistics2/screens/ingredient_buffer.json b/src/main/resources/assets/appliedenergistics2/screens/ingredient_buffer.json
new file mode 100644
index 000000000..63f9bb7ba
--- /dev/null
+++ b/src/main/resources/assets/appliedenergistics2/screens/ingredient_buffer.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "schema.json",
+ "includes": ["common/common.json", "common/player_inventory.json"],
+ "background": {
+ "texture": "gui/ingredient_buffer.png",
+ "srcRect": [0, 0, 176, 222]
+ },
+ "slots": {
+ "STORAGE": {
+ "left": 8,
+ "top": 108,
+ "grid": "HORIZONTAL"
+ }
+ },
+ "text": {
+ "dialog_title": {
+ "text": {
+ "translate": "ae2fc.gui.ingredient_buffer"
+ },
+ "position": {
+ "left": 8,
+ "top": 6
+ }
+ }
+ }
+}
diff --git a/src/main/resources/assets/appliedenergistics2/screens/large_ingredient_buffer.json b/src/main/resources/assets/appliedenergistics2/screens/large_ingredient_buffer.json
new file mode 100644
index 000000000..462073c55
--- /dev/null
+++ b/src/main/resources/assets/appliedenergistics2/screens/large_ingredient_buffer.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "schema.json",
+ "includes": ["common/common.json", "common/player_inventory.json"],
+ "background": {
+ "texture": "gui/large_ingredient_buffer.png",
+ "srcRect": [0, 0, 176, 222]
+ },
+ "slots": {
+ "STORAGE": {
+ "left": 8,
+ "top": 72,
+ "grid": "BREAK_AFTER_9COLS"
+ }
+ },
+ "text": {
+ "dialog_title": {
+ "text": {
+ "translate": "ae2fc.gui.large_ingredient_buffer"
+ },
+ "position": {
+ "left": 8,
+ "top": 6
+ }
+ }
+ }
+}
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/burette.png b/src/main/resources/assets/appliedenergistics2/textures/gui/burette.png
new file mode 100644
index 000000000..7238bf637
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/burette.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler.png
new file mode 100644
index 000000000..6435daecd
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler2.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler2.png
new file mode 100644
index 000000000..023c41a2d
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler2.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler3.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler3.png
new file mode 100644
index 000000000..795d0306d
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler3.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler4.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler4.png
new file mode 100644
index 000000000..a65acc2bc
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_assembler4.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_level_maintainer.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_level_maintainer.png
new file mode 100644
index 000000000..e575eb122
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_level_maintainer.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_packet_decoder.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_packet_decoder.png
new file mode 100644
index 000000000..4e17ee57f
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_packet_decoder.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_pattern_encoder.png b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_pattern_encoder.png
new file mode 100644
index 000000000..045611f80
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/fluid_pattern_encoder.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/ingredient_buffer.png b/src/main/resources/assets/appliedenergistics2/textures/gui/ingredient_buffer.png
new file mode 100644
index 000000000..e1d351945
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/ingredient_buffer.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/large_ingredient_buffer.png b/src/main/resources/assets/appliedenergistics2/textures/gui/large_ingredient_buffer.png
new file mode 100644
index 000000000..f7763ab05
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/large_ingredient_buffer.png differ
diff --git a/src/main/resources/assets/appliedenergistics2/textures/gui/states.png b/src/main/resources/assets/appliedenergistics2/textures/gui/states.png
new file mode 100644
index 000000000..8c25f121e
Binary files /dev/null and b/src/main/resources/assets/appliedenergistics2/textures/gui/states.png differ
diff --git a/src/main/resources/data/ae2fc/recipes/dual_interface.json b/src/main/resources/data/ae2fc/recipes/dual_interface.json
new file mode 100644
index 000000000..28bbaa329
--- /dev/null
+++ b/src/main/resources/data/ae2fc/recipes/dual_interface.json
@@ -0,0 +1,15 @@
+{
+ "type": "minecraft:crafting_shapeless",
+ "ingredients": [
+ {
+ "tag": "appliedenergistics2:interface"
+ },
+ {
+ "tag": "appliedenergistics2:fluid_interface"
+ }
+ ],
+ "result": {
+ "item": "ae2fc:dual_interface",
+ "nbt": {}
+ }
+}
diff --git a/src/main/resources/data/ae2fc/recipes/dual_interface_alter.json b/src/main/resources/data/ae2fc/recipes/dual_interface_alter.json
new file mode 100644
index 000000000..56e59a894
--- /dev/null
+++ b/src/main/resources/data/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"
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/data/ae2fc/recipes/fluid_discretizer.json b/src/main/resources/data/ae2fc/recipes/fluid_discretizer.json
new file mode 100644
index 000000000..7d39d207e
--- /dev/null
+++ b/src/main/resources/data/ae2fc/recipes/fluid_discretizer.json
@@ -0,0 +1,29 @@
+{
+ "type": "minecraft:crafting_shaped",
+ "result": {
+ "item": "ae2fc:fluid_discretizer",
+ "count": 1
+ },
+ "pattern": [
+ "ipi",
+ "smt",
+ "ipi"
+ ],
+ "key": {
+ "i": {
+ "tag": "forge:ingots/iron"
+ },
+ "p": {
+ "item": "appliedenergistics2:engineering_processor"
+ },
+ "s": {
+ "item": "appliedenergistics2:fluid_storage_bus"
+ },
+ "m": {
+ "item": "appliedenergistics2:condenser"
+ },
+ "t": {
+ "item": "appliedenergistics2:storage_bus"
+ }
+ }
+}
diff --git a/src/main/resources/data/ae2fc/recipes/fluid_packet_decoder.json b/src/main/resources/data/ae2fc/recipes/fluid_packet_decoder.json
new file mode 100644
index 000000000..0aaac741c
--- /dev/null
+++ b/src/main/resources/data/ae2fc/recipes/fluid_packet_decoder.json
@@ -0,0 +1,29 @@
+{
+ "type": "minecraft:crafting_shaped",
+ "result": {
+ "item": "ae2fc:fluid_packet_decoder",
+ "count": 1
+ },
+ "pattern": [
+ "ihi",
+ "cfc",
+ "ipi"
+ ],
+ "key": {
+ "i": {
+ "tag": "forge:ingots/iron"
+ },
+ "h": {
+ "item": "minecraft:hopper"
+ },
+ "c": {
+ "item": "appliedenergistics2:fluix_glass_cable"
+ },
+ "f": {
+ "tag": "appliedenergistics2:fluid_interface"
+ },
+ "p": {
+ "item": "appliedenergistics2:calculation_processor"
+ }
+ }
+}
diff --git a/src/main/resources/data/ae2fc/recipes/ingredient_buffer.json b/src/main/resources/data/ae2fc/recipes/ingredient_buffer.json
new file mode 100644
index 000000000..1447b1a73
--- /dev/null
+++ b/src/main/resources/data/ae2fc/recipes/ingredient_buffer.json
@@ -0,0 +1,32 @@
+{
+ "type": "minecraft:crafting_shaped",
+ "result": {
+ "item": "ae2fc:ingredient_buffer",
+ "count": 1
+ },
+ "pattern": [
+ "ili",
+ "agf",
+ "isi"
+ ],
+ "key": {
+ "i": {
+ "tag": "forge:ingots/iron"
+ },
+ "g": {
+ "item": "appliedenergistics2:quartz_glass"
+ },
+ "l": {
+ "item": "appliedenergistics2:1k_cell_component"
+ },
+ "a": {
+ "item": "appliedenergistics2:annihilation_core"
+ },
+ "f": {
+ "item": "appliedenergistics2:formation_core"
+ },
+ "s": {
+ "item": "appliedenergistics2:1k_fluid_cell_component"
+ }
+ }
+}
diff --git a/src/main/resources/data/ae2fc/recipes/large_ingredient_buffer.json b/src/main/resources/data/ae2fc/recipes/large_ingredient_buffer.json
new file mode 100644
index 000000000..d04428e76
--- /dev/null
+++ b/src/main/resources/data/ae2fc/recipes/large_ingredient_buffer.json
@@ -0,0 +1,23 @@
+{
+ "type": "minecraft:crafting_shaped",
+ "result": {
+ "item": "ae2fc:large_ingredient_buffer",
+ "count": 1
+ },
+ "pattern": [
+ "bgb",
+ "geg",
+ "bgb"
+ ],
+ "key": {
+ "b": {
+ "item": "ae2fc:ingredient_buffer"
+ },
+ "g": {
+ "item": "appliedenergistics2:quartz_glass"
+ },
+ "e": {
+ "item": "appliedenergistics2:engineering_processor"
+ }
+ }
+}
diff --git a/src/main/resources/data/ae2fc/recipes/part_dual_interface.json b/src/main/resources/data/ae2fc/recipes/part_dual_interface.json
new file mode 100644
index 000000000..dc5d91f7b
--- /dev/null
+++ b/src/main/resources/data/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/data/ae2fc/recipes/part_fluid_pattern_terminal.json b/src/main/resources/data/ae2fc/recipes/part_fluid_pattern_terminal.json
new file mode 100644
index 000000000..648e007ad
--- /dev/null
+++ b/src/main/resources/data/ae2fc/recipes/part_fluid_pattern_terminal.json
@@ -0,0 +1,14 @@
+{
+ "type": "minecraft:crafting_shapeless",
+ "ingredients": [
+ {
+ "item": "appliedenergistics2:pattern_terminal"
+ },
+ {
+ "item": "ae2fc:fluid_packet_decoder"
+ }
+ ],
+ "result": {
+ "item": "ae2fc:part_fluid_pattern_terminal"
+ }
+}
diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png
new file mode 100644
index 000000000..129cbc24f
Binary files /dev/null and b/src/main/resources/logo.png differ
diff --git a/src/main/resources/mixins.ae2fc.json b/src/main/resources/mixins.ae2fc.json
new file mode 100644
index 000000000..f4d2e44e9
--- /dev/null
+++ b/src/main/resources/mixins.ae2fc.json
@@ -0,0 +1,25 @@
+{
+ "required": true,
+ "package": "com.glodblock.github.mixins",
+ "refmap": "mixins.ae2fc.refmap.json",
+ "compatibilityLevel": "JAVA_8",
+ "mixins": [
+ "ApiCraftingMixin",
+ "CraftConfirmContainerMixin",
+ "CraftingCpuMixin",
+ "CraftingGridCacheMixin",
+ "CraftingTreeNodeMixin",
+ "DualityInterfaceMixin",
+ "InterfaceSlotMixin",
+ "InterfaceTerminalContainerMixin",
+ "PatternSlotPacketMixin",
+ "RestrictedInputSlotMixin"
+ ],
+ "client": [
+ "AESubScreenMixin",
+ "CraftConfirmTableRendererMixin",
+ "CraftingStatusTableRendererMixin",
+ "EncodedPatternBakedModelMixin"
+ ],
+ "server": []
+}
\ 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
+ }
+}