diff --git a/.github/actions/build_setup/action.yml b/.github/actions/build_setup/action.yml new file mode 100644 index 00000000000..7409143dc18 --- /dev/null +++ b/.github/actions/build_setup/action.yml @@ -0,0 +1,30 @@ +name: Build Setup +description: Setup for standard Java builds + +inputs: + update-cache: + description: If cache should be updated + required: false + default: false + +runs: + using: 'composite' + + steps: + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 17 + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + with: + cache-write-only: ${{ inputs.update-cache }} + generate-job-summary: false + gradle-home-cache-includes: | + caches + caches/retro_futura_gradle + notifications + jdks + wrapper diff --git a/.github/workflows/format_java.yml b/.github/workflows/format_java.yml new file mode 100644 index 00000000000..e8d233df439 --- /dev/null +++ b/.github/workflows/format_java.yml @@ -0,0 +1,29 @@ +# Runs formatting requirements +name: Java Formatting + +on: + push: + branches: + - master + paths: ['src/main/java/**', 'src/test/**'] + pull_request: + paths: ['src/main/java/**', 'src/test/**'] + +concurrency: + group: formatting-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + formatting: + name: Formatting + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Build + uses: ./.github/actions/build_setup + + - name: Run Spotless Formatting Check with Gradle + run: ./gradlew spotlessCheck --warning-mode all --build-cache diff --git a/.github/workflows/manage_pr_labels.yml b/.github/workflows/manage_pr_labels.yml new file mode 100644 index 00000000000..7dcddd620b4 --- /dev/null +++ b/.github/workflows/manage_pr_labels.yml @@ -0,0 +1,39 @@ +# Manages labels on PRs before allowing merging +name: Pull Request Labels + +on: + pull_request: + types: + - opened + - labeled + - unlabeled + - synchronize + +# if a second commit is pushed quickly after the first, cancel the first one's build +concurrency: + group: pr-labels-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + Labels: + runs-on: ubuntu-latest + + permissions: + pull-requests: read # needed to utilize required-labels + + steps: + - name: Check for Merge-Blocking Labels # blocks merge if present + uses: mheap/github-action-required-labels@v5 + with: + mode: exactly + count: 0 + labels: 'status: do not merge' + exit_type: failure + + - name: Check for Required Labels # require at least one of these labels + uses: mheap/github-action-required-labels@v5 + with: + mode: minimum + count: 1 + labels: 'type: feature, type: bug, type: refactor, type: translation, ignore changelog' + exit_type: failure diff --git a/.github/workflows/nuclear_test_release.yml b/.github/workflows/nuclear_test_release.yml new file mode 100644 index 00000000000..5f8d389ee63 --- /dev/null +++ b/.github/workflows/nuclear_test_release.yml @@ -0,0 +1,49 @@ +name: Nuclear Test Release + +on: + push: + branches: + - nuclear-fission +jobs: + build: + runs-on: ubuntu-latest + + permissions: + contents: write # needed to create GitHub releases + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Declare some variables + id: vars + shell: bash + run: | + echo "::set-output name=sha_short::$(git rev-parse --short $GITHUB_SHA)" + + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + + - name: Build Project + uses: gradle/gradle-build-action@v2 + with: + arguments: 'build --build-cache --daemon' # use the daemon here so the rest of the process is faster + generate-job-summary: false + gradle-home-cache-includes: | + caches + jdks + notifications + wrapper + + - name: Publish to GitHub + uses: softprops/action-gh-release@v1 + with: + files: "build/libs/*.jar" + fail_on_unmatched_files: true + prerelease: true + tag_name: "nuclear-testing" + name: "Nuclear Testing ${{ steps.vars.outputs.sha_short }}" + body: "Testing Pre-release for GTCEu Nuclear Update" diff --git a/.github/workflows/pr_artifact.yml b/.github/workflows/pr_artifact.yml deleted file mode 100644 index 54187e2e326..00000000000 --- a/.github/workflows/pr_artifact.yml +++ /dev/null @@ -1,77 +0,0 @@ -# Creates a built artifact jar for apropriately labeled Pull Requests -name: PR Artifact - -on: - pull_request: - types: - - labeled - - synchronize - -# if a second commit is pushed quickly after the first, cancel the first one's build -concurrency: - group: build-artifact-${{github.head_ref}} - cancel-in-progress: true - -env: - ARTIFACT_NAME: 'gregtech-dev' - NOTIFICATION_BODY: | - This Pull Request has automatic artifact deployment enabled. Download the latest build [here](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}), and extract the contents. - -jobs: - Build_Artifact: - if: contains(github.event.pull_request.labels.*.name, format('deployment{0} artifact', ':')) - runs-on: ubuntu-latest - - permissions: - pull-requests: write # needed to make a comment - contents: read # needed to checkout the repo if defining other permissions - - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '17' - - - name: Build Project - uses: gradle/gradle-build-action@v2 - with: - arguments: 'build --build-cache --no-daemon ' # disable daemon since only one gradle operation will happen - generate-job-summary: false - gradle-home-cache-includes: | - caches - jdks - notifications - wrapper - - - name: Determine Artifact Name - id: artifact_name - run: | - FILE=(build/libs/*) - echo "artifact_name=${FILE[0]}" >> $GITHUB_OUTPUT - - - name: Publish Artifact - uses: actions/upload-artifact@v3 - with: - name: "${{env.ARTIFACT_NAME}}-${{github.head_ref}}-${{github.sha}}" - path: "${{steps.artifact_name.outputs.artifact_name}}" - if-no-files-found: error - - - name: Find Existing Comment - uses: peter-evans/find-comment@v2 - id: find-existing - with: - issue-number: ${{github.event.pull_request.number}} - comment-author: 'github-actions[bot]' - body-includes: 'Download the latest build [here]' - - - name: Create or Update Artifact Publish Notification - uses: peter-evans/create-or-update-comment@v2 - with: - issue-number: ${{github.event.pull_request.number}} - comment-id: ${{steps.find-existing.outputs.comment-id}} - edit-mode: replace - body: ${{env.NOTIFICATION_BODY}} diff --git a/.github/workflows/pr_labels.yml b/.github/workflows/pr_labels.yml deleted file mode 100644 index a47b267ec3c..00000000000 --- a/.github/workflows/pr_labels.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Manages labels on PRs before allowing merging -name: PR labels - -on: - pull_request: - types: [opened, labeled, unlabeled, synchronize] - -# if a second commit is pushed quickly after the first, cancel the first one's build -concurrency: - group: pr-labels-${{github.head_ref}} - cancel-in-progress: true - -jobs: - Labels: - runs-on: ubuntu-latest - - permissions: - pull-requests: read # needed to utilize required-labels - - steps: - - name: Merge-Blocking Labels # blocks merge if present - uses: mheap/github-action-required-labels@v3 - with: - mode: exactly - count: 0 - labels: "status: do not merge" - exit_type: failure - - - name: Required Changelog Labels # require at least one of these labels - uses: mheap/github-action-required-labels@v3 - with: - mode: minimum - count: 1 - labels: "type: feature, type: bug, type: refactor, type: translation, ignore changelog" - exit_type: failure diff --git a/.github/workflows/pr_testing.yml b/.github/workflows/pr_testing.yml deleted file mode 100644 index 7761d2a8555..00000000000 --- a/.github/workflows/pr_testing.yml +++ /dev/null @@ -1,60 +0,0 @@ -# Runs tests and other checks on Pull Requests -name: PR Testing - -on: - pull_request: - branches: - - master - -# if a second commit is pushed quickly after the first, cancel the first one's build -concurrency: - group: pr-testing-${{github.head_ref}} - cancel-in-progress: true - -jobs: - Test: - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '17' - - - name: Run Tests - uses: gradle/gradle-build-action@v2 - with: - arguments: 'test --build-cache --no-daemon' # disable daemon since only one gradle operation will happen - generate-job-summary: false - gradle-home-cache-includes: | - caches - jdks - notifications - wrapper - - Formatting: - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '17' - - - name: Run Spotless - uses: gradle/gradle-build-action@v2 - with: - arguments: 'spotlessCheck --build-cache' - gradle-home-cache-includes: | - caches - jdks - notifications - wrapper diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index df5366133a1..00000000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,87 +0,0 @@ -# Publishes built jars to distribution platforms -name: Publish - -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' # any semver tag, e.g. v1.2.3 - -env: - # link to the changelog with a format code for the version - CHANGELOG_LOCATION: "Changelog is available [here](https://github.com/${{github.repository}}/releases/tag/${{github.ref_name}})" - # type of release - RELEASE_TYPE: "beta" - -jobs: - Publish: - runs-on: ubuntu-latest - - permissions: - contents: write # needed to create GitHub releases - - steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - - name: Check for Duplicate Tags - run: | - if git rev-parse -q --verify "refs/tags/${{ github.ref }}" >/dev/null; then - echo "Tag already exists. A version bump is required." - exit 1 - fi - - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '17' - - - name: Build Project - uses: gradle/gradle-build-action@v2 - with: - arguments: 'build --build-cache --daemon' # use the daemon here so the rest of the process is faster - generate-job-summary: false - gradle-home-cache-includes: | - caches - jdks - notifications - wrapper - - - name: Publish to GitHub - uses: softprops/action-gh-release@v1 - with: - files: "build/libs/*.jar" - generate_release_notes: true - fail_on_unmatched_files: true - - - name: Publish to Curseforge - uses: gradle/gradle-build-action@v2 - env: - CURSEFORGE_API_KEY: "${{secrets.CURSEFORGE_API_KEY}}" - CURSEFORGE_PROJECT_ID: "${{secrets.CURSEFORGE_PROJECT_ID}}" - CHANGELOG_LOCATION: "${{env.CHANGELOG_LOCATION}}" - RELEASE_TYPE: "${{env.RELEASE_TYPE}}" - with: - arguments: 'curseforge --daemon' - generate-job-summary: false - gradle-home-cache-includes: | - caches - jdks - notifications - wrapper - - - name: Publish to Modrinth - uses: gradle/gradle-build-action@v2 - env: - MODRINTH_API_KEY: "${{secrets.MODRINTH_API_KEY}}" - MODRINTH_PROJECT_ID: "${{secrets.MODRINTH_PROJECT_ID}}" - CHANGELOG_LOCATION: "${{env.CHANGELOG_LOCATION}}" - RELEASE_TYPE: "${{env.RELEASE_TYPE}}" - with: - arguments: 'modrinth --daemon' - generate-job-summary: false - gradle-home-cache-includes: | - caches - jdks - notifications - wrapper diff --git a/.github/workflows/publish_project.yml b/.github/workflows/publish_project.yml new file mode 100644 index 00000000000..a6bb305d5bb --- /dev/null +++ b/.github/workflows/publish_project.yml @@ -0,0 +1,64 @@ +# Publishes the project to GitHub Releases, CurseForge, and Modrinth +name: Publish Project + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' # any SemVer tag, e.g. v1.2.3 + +env: + # link to the changelog with a format code for the version + CHANGELOG_LOCATION: "Changelog is available [here](https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }})" + # type of release + RELEASE_TYPE: 'beta' + +concurrency: + group: publish-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + + permissions: + contents: write # needed to create GitHub releases + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Build + uses: ./.github/actions/build_setup + + - name: Build Project + run: ./gradlew build --warning-mode all --build-cache + + - name: Publish to GitHub + uses: softprops/action-gh-release@v1 + with: + files: "build/libs/*.jar" + generate_release_notes: true + fail_on_unmatched_files: true + + - name: Publish to Maven + env: + MAVEN_USER: "${{ secrets.MAVEN_USER }}" + MAVEN_PASSWORD: "${{ secrets.MAVEN_PASSWORD }}" + run: ./gradlew publish + + - name: Publish to Curseforge + env: + CURSEFORGE_API_KEY: "${{ secrets.CURSEFORGE_API_KEY }}" + CURSEFORGE_PROJECT_ID: "${{ secrets.CURSEFORGE_PROJECT_ID }}" + CHANGELOG_LOCATION: "${{ env.CHANGELOG_LOCATION }}" + RELEASE_TYPE: "${{ env.RELEASE_TYPE }}" + run: ./gradlew curseforge --warning-mode all --build-cache + + - name: Publish to Modrinth + env: + MODRINTH_API_KEY: "${{ secrets.MODRINTH_API_KEY }}" + MODRINTH_PROJECT_ID: "${{ secrets.MODRINTH_PROJECT_ID }}" + CHANGELOG_LOCATION: "${{ env.CHANGELOG_LOCATION }}" + RELEASE_TYPE: "${{ env.RELEASE_TYPE }}" + run: ./gradlew modrinth --warning-mode all --build-cache diff --git a/.github/workflows/test_java.yml b/.github/workflows/test_java.yml new file mode 100644 index 00000000000..11bc42e8067 --- /dev/null +++ b/.github/workflows/test_java.yml @@ -0,0 +1,31 @@ +# Runs tests +name: Java Tests + +on: + push: + branches: + - master + paths: ['src/main/java/**', 'src/test/**', 'src/api/java/**', 'gradle/**', '**.gradle', 'gradle.properties', + 'gradlew**', 'src/main/resources/*_at.cfg'] + pull_request: + paths: ['src/main/java/**', 'src/test/**', 'src/api/java/**', 'gradle/**', '**.gradle', 'gradle.properties', + 'gradlew**', 'src/main/resources/*_at.cfg'] + +concurrency: + group: tests-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Build + uses: ./.github/actions/build_setup + + - name: Run Tests with Gradle + run: ./gradlew test --warning-mode all --build-cache diff --git a/.github/workflows/update_buildscript.yml b/.github/workflows/update_buildscript.yml index f8107f648d5..45fa46819d2 100644 --- a/.github/workflows/update_buildscript.yml +++ b/.github/workflows/update_buildscript.yml @@ -1,41 +1,33 @@ # Checks daily to see if the buildscript is in need of an update -name: Buildscript Updating +name: Update Buildscript on: workflow_dispatch: schedule: - - cron: "0 0 * * *" # "min hr day month year", so run once per day + - cron: '0 0 * * *' # "min hr day month year", so run once per day jobs: buildscript-update: runs-on: ubuntu-latest + # Avoid running this workflow on forks if: github.repository == 'GregTechCEu/GregTech' + permissions: contents: write pull-requests: write + steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout Repository + uses: actions/checkout@v4 - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '17' + - name: Setup Build + uses: ./.github/actions/build_setup - - name: Run script updater - uses: gradle/gradle-build-action@v2 - with: - arguments: 'updateBuildScript' - generate-job-summary: false - gradle-home-cache-includes: | - caches - jdks - notifications - wrapper + - name: Run Buildscript Updater + run: ./gradlew updateBuildScript --warning-mode all --build-cache - - name: Get new script version + - name: Get New Buildscript Version id: version-check run: | new_version=$(head -1 build.gradle | sed -r 's|//version: (.*)|\1|g') @@ -43,7 +35,7 @@ jobs: - name: Create Pull Request id: create-pull-request - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@v5 env: GITHUB_TOKEN: ${{ secrets.BUILDSCRIPT_UPDATER_TOKEN }} with: @@ -56,11 +48,8 @@ jobs: body: This pull request is created by the buildscript-update workflow labels: ignore changelog - - name: Enable pull request auto-merge - id: pull-request-auto-merge + - name: Enable Pull-Request Auto-Merge if: steps.create-pull-request.outputs.pull-request-operation == 'created' - uses: peter-evans/enable-pull-request-automerge@v3 - with: - token: ${{ secrets.BUILDSCRIPT_UPDATER_TOKEN }} - pull-request-number: ${{ steps.create-pull-request.outputs.pull-request-number }} - merge-method: squash + run: gh pr merge --squash --auto "${{ steps.create-pull-request.outputs.pull-request-number }}" + env: + GH_TOKEN: ${{ secrets.BUILDSCRIPT_UPDATER_TOKEN }} diff --git a/.github/workflows/update_gradle_cache.yml b/.github/workflows/update_gradle_cache.yml index 9853000d7ac..ba93b16779a 100644 --- a/.github/workflows/update_gradle_cache.yml +++ b/.github/workflows/update_gradle_cache.yml @@ -1,39 +1,30 @@ -# Updates the Gradle Cache when relevant files change +# Updates the Gradle Cache when necessary name: Update Gradle Cache on: - workflow_dispatch: push: branches: - master - paths: - - "gradle**" # covers gradle folder, gradle.properties, gradlew - - "build.gradle*" - - "settings.gradle*" - - "src/main/resources/*_at.cfg" # access transformers + paths: ['gradle/**', '**.gradle', 'gradle.properties', 'gradlew**', 'src/main/resources/*_at.cfg'] + workflow_dispatch: + +concurrency: + group: gradle-cache-${{ github.ref }} + cancel-in-progress: true jobs: - Update_Cache: + update-cache: + name: Update Grade Cache runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: Setup Java - uses: actions/setup-java@v3 + - name: Setup Build + uses: ./.github/actions/build_setup with: - distribution: 'zulu' - java-version: '17' + update-cache: true - - name: Update Cache - uses: gradle/gradle-build-action@v2 - with: - arguments: 'test --build-cache --no-daemon' # disable daemon since only one gradle operation will happen - generate-job-summary: false - gradle-home-cache-includes: | - caches - jdks - notifications - wrapper - cache-write-only: true + - name: Build Project with Gradle + run: ./gradlew assemble --warning-mode all --build-cache diff --git a/.github/workflows/validate_gradle_wrapper.yml b/.github/workflows/validate_gradle_wrapper.yml index 6f7a9d4cbe9..591e487d7d5 100644 --- a/.github/workflows/validate_gradle_wrapper.yml +++ b/.github/workflows/validate_gradle_wrapper.yml @@ -1,4 +1,4 @@ -# Validates the integrity of the Gradle wrapper +# Validates the integrity of the Gradle Wrapper name: Validate Gradle Wrapper on: @@ -13,9 +13,8 @@ on: paths: - 'gradle/**' -# if a second commit is pushed quickly after the first, cancel the first one's build concurrency: - group: gradle-wrapper-validation-${{github.head_ref}} + group: gradle-wrapper-validation-${{ github.head_ref || github.ref }} cancel-in-progress: true jobs: @@ -24,7 +23,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 diff --git a/README.md b/README.md index 5876b6d1b60..8a4fcfa7209 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Anything else? Sure come to [Discord](https://discord.gg/bWSWuYvURP). ## Credited Works Heating Coil Textures, Wooden Forms, World Accelerators, and the Extreme Combustion Engine are from the **[GregTech: New Horizons Modpack](https://www.curseforge.com/minecraft/modpacks/gt-new-horizons)**. -Primitive Water Pump and Super Tank GUI Textures are from the **[IMPACT: GREGTECH EDITION Modpack](https://gtimpact.space/)**. +Primitive Water Pump and Super Tank GUI Textures are from the **[IMPACT: GREGTECH EDITION Modpack](https://gt-impact.github.io/)**. Ender Fluid Link Cover, Auto-Maintenance Hatch, Optical Fiber, and Data Bank Textures are from **[TecTech](https://github.com/Technus/TecTech)**. diff --git a/build.gradle b/build.gradle index 29b2ba602bf..8ea7b057a37 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -//version: 1702244235 +//version: 1707682661 /* * DO NOT CHANGE THIS FILE! * Also, you may replace this file at any time if there is an update available. @@ -24,9 +24,9 @@ plugins { id 'eclipse' id 'maven-publish' id 'org.jetbrains.gradle.plugin.idea-ext' version '1.1.7' - id 'com.gtnewhorizons.retrofuturagradle' version '1.3.25' - id 'net.darkhax.curseforgegradle' version '1.1.17' apply false - id 'com.modrinth.minotaur' version '2.8.6' apply false + id 'com.gtnewhorizons.retrofuturagradle' version '1.3.33' + id 'net.darkhax.curseforgegradle' version '1.1.18' apply false + id 'com.modrinth.minotaur' version '2.8.7' apply false id 'com.diffplug.spotless' version '6.13.0' apply false id 'com.palantir.git-version' version '3.0.0' apply false id 'com.github.johnrengelman.shadow' version '8.1.1' apply false @@ -62,6 +62,7 @@ propertyDefaultIfUnset("generateGradleTokenClass", "") propertyDefaultIfUnset("gradleTokenModId", "") propertyDefaultIfUnset("gradleTokenModName", "") propertyDefaultIfUnset("gradleTokenVersion", "") +propertyDefaultIfUnset("useSrcApiPath", false) propertyDefaultIfUnset("includeWellKnownRepositories", true) propertyDefaultIfUnset("includeCommonDevEnvMods", true) propertyDefaultIfUnset("noPublishedSources", false) @@ -72,6 +73,7 @@ propertyDefaultIfUnset("usesShadowedDependencies", false) propertyDefaultIfUnset("minimizeShadowedDependencies", true) propertyDefaultIfUnset("relocateShadowedDependencies", true) propertyDefaultIfUnset("separateRunDirectories", false) +propertyDefaultIfUnset("versionDisplayFormat", '$MOD_NAME \u2212 $VERSION') propertyDefaultIfUnsetWithEnvVar("modrinthProjectId", "", "MODRINTH_PROJECT_ID") propertyDefaultIfUnset("modrinthRelations", "") propertyDefaultIfUnsetWithEnvVar("curseForgeProjectId", "", "CURSEFORGE_PROJECT_ID") @@ -104,9 +106,16 @@ if (!getFile(targetPackageJava).exists() && !getFile(targetPackageScala).exists( } if (apiPackage) { - targetPackageJava = javaSourceDir + modGroupPath + '/' + apiPackagePath - targetPackageScala = scalaSourceDir + modGroupPath + '/' + apiPackagePath - targetPackageKotlin = kotlinSourceDir + modGroupPath + '/' + apiPackagePath + final String endApiPath = modGroupPath + '/' + apiPackagePath + if (useSrcApiPath) { + targetPackageJava = 'src/api/java/' + endApiPath + targetPackageScala = 'src/api/scala/' + endApiPath + targetPackageKotlin = 'src/api/kotlin/' + endApiPath + } else { + targetPackageJava = javaSourceDir + endApiPath + targetPackageScala = scalaSourceDir + endApiPath + targetPackageKotlin = kotlinSourceDir + endApiPath + } if (!getFile(targetPackageJava).exists() && !getFile(targetPackageScala).exists() && !getFile(targetPackageKotlin).exists()) { throw new GradleException("Could not resolve \"apiPackage\"! Could not find ${targetPackageJava} or ${targetPackageScala} or ${targetPackageKotlin}") } @@ -435,8 +444,11 @@ repositories { } maven { name 'GTNH Maven' - url 'http://jenkins.usrv.eu:8081/nexus/content/groups/public' - allowInsecureProtocol = true + url 'https://nexus.gtnewhorizons.com/repository/public/' + } + maven { + name 'GTCEu Maven' + url 'https://maven.gtceu.com' } } if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) { @@ -465,9 +477,25 @@ configurations { config.extendsFrom(shadowCompile) } } + + create("runtimeOnlyNonPublishable") { + description = "Runtime only dependencies that are not published alongside the jar" + canBeConsumed = false + canBeResolved = false + } + create("devOnlyNonPublishable") { + description = "Runtime and compiletime dependencies that are not published alongside the jar (compileOnly + runtimeOnlyNonPublishable)" + canBeConsumed = false + canBeResolved = false + } + + compileOnly.extendsFrom(devOnlyNonPublishable) + runtimeOnlyNonPublishable.extendsFrom(devOnlyNonPublishable) + runtimeClasspath.extendsFrom(runtimeOnlyNonPublishable) + testRuntimeClasspath.extendsFrom(runtimeOnlyNonPublishable) } -String mixinProviderSpec = 'zone.rong:mixinbooter:8.9' +String mixinProviderSpec = 'zone.rong:mixinbooter:9.1' dependencies { if (usesMixins.toBoolean()) { annotationProcessor 'org.ow2.asm:asm-debug-all:5.2' @@ -485,7 +513,7 @@ dependencies { transitive = false } } else if (forceEnableMixins.toBoolean()) { - runtimeOnly(mixinProviderSpec) + runtimeOnlyNonPublishable(mixinProviderSpec) } if (enableJUnit.toBoolean()) { @@ -495,8 +523,8 @@ dependencies { } if (enableModernJavaSyntax.toBoolean()) { - annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:1.0.0' - compileOnly('com.github.bsideup.jabel:jabel-javac-plugin:1.0.0') { + annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:1.0.1' + compileOnly('com.github.bsideup.jabel:jabel-javac-plugin:1.0.1') { transitive = false } // workaround for https://github.com/bsideup/jabel/issues/174 @@ -505,8 +533,8 @@ dependencies { patchedMinecraft 'me.eigenraven.java8unsupported:java-8-unsupported-shim:1.0.0' // allow Jabel to work in tests - testAnnotationProcessor "com.github.bsideup.jabel:jabel-javac-plugin:1.0.0" - testCompileOnly("com.github.bsideup.jabel:jabel-javac-plugin:1.0.0") { + testAnnotationProcessor "com.github.bsideup.jabel:jabel-javac-plugin:1.0.1" + testCompileOnly("com.github.bsideup.jabel:jabel-javac-plugin:1.0.1") { transitive = false // We only care about the 1 annotation class } testCompileOnly "me.eigenraven.java8unsupported:java-8-unsupported-shim:1.0.0" @@ -519,9 +547,13 @@ dependencies { } if (includeCommonDevEnvMods.toBoolean()) { - implementation 'mezz.jei:jei_1.12.2:4.16.1.302' - //noinspection DependencyNotationArgument - implementation rfg.deobf('curse.maven:top-245211:2667280') // TOP 1.4.28 + if (!(modId.equals('jei'))) { + implementation 'mezz.jei:jei_1.12.2:4.16.1.302' + } + if (!(modId.equals('theoneprobe'))) { + //noinspection DependencyNotationArgument + implementation rfg.deobf('curse.maven:top-245211:2667280') // TOP 1.4.28 + } } } @@ -533,6 +565,12 @@ pluginManager.withPlugin('org.jetbrains.kotlin.kapt') { } } +configurations.configureEach { + resolutionStrategy.dependencySubstitution { + substitute module('org.scala-lang:scala-library:2.11.1') using module('org.scala-lang:scala-library:2.11.5') because('To allow mixing with Java 8 targets') + } +} + if (getFile('dependencies.gradle').exists()) { apply from: 'dependencies.gradle' } else if (getFile('dependencies.gradle.kts').exists()) { @@ -674,6 +712,19 @@ jar { it.isDirectory() ? it : zipTree(it) } } + + if (useSrcApiPath && apiPackage) { + from sourceSets.api.output + dependsOn apiClasses + + include "${modGroupPath}/**" + include "assets/**" + include "mcmod.info" + include "pack.mcmeta" + if (accessTransformersFile) { + include "META-INF/${accessTransformersFile}" + } + } } // Configure default run tasks @@ -690,12 +741,21 @@ if (separateRunDirectories.toBoolean()) { // Create API library jar tasks.register('apiJar', Jar) { archiveClassifier.set 'api' - from(sourceSets.main.java) { - include "${modGroupPath}/${apiPackagePath}/**" - } + if (useSrcApiPath) { + from(sourceSets.api.java) { + include "${modGroupPath}/${apiPackagePath}/**" + } + from(sourceSets.api.output) { + include "${modGroupPath}/${apiPackagePath}/**" + } + } else { + from(sourceSets.main.java) { + include "${modGroupPath}/${apiPackagePath}/**" + } - from(sourceSets.main.output) { - include "${modGroupPath}/${apiPackagePath}/**" + from(sourceSets.main.output) { + include "${modGroupPath}/${apiPackagePath}/**" + } } } @@ -889,7 +949,7 @@ if (cfApiKey.isPresent() || deploymentDebug.toBoolean()) { def changelogFile = getChangelog() def changelogRaw = changelogFile.exists() ? changelogFile.getText('UTF-8') : "" - mainFile.displayName = "${modName}: ${modVersion}" + mainFile.displayName = versionDisplayFormat.replace('$MOD_NAME', modName).replace('$VERSION', modVersion) mainFile.releaseType = getReleaseType() mainFile.changelog = changelogRaw mainFile.changelogType = 'markdown' @@ -905,6 +965,12 @@ if (cfApiKey.isPresent() || deploymentDebug.toBoolean()) { } String[] parts = dep.split(':') String type = parts[0], slug = parts[1] + def types = [ + 'req' : 'requiredDependency', 'required': 'requiredDependency', + 'opt' : 'optionalDependency', 'optional': 'optionalDependency', + 'embed' : 'embeddedLibrary', 'embedded': 'embeddedLibrary', + 'incomp': 'incompatible', 'fail' : 'incompatible'] + if (types.containsKey(type)) type = types[type] if (!(type in ['requiredDependency', 'embeddedLibrary', 'optionalDependency', 'tool', 'incompatible'])) { throw new Exception('Invalid Curseforge dependency type: ' + type) } @@ -929,6 +995,7 @@ if (modrinthApiKey.isPresent() || deploymentDebug.toBoolean()) { modrinth { token = modrinthApiKey.getOrElse('debug_token') projectId = modrinthProjectId + versionName = versionDisplayFormat.replace('$MOD_NAME', modName).replace('$VERSION', modVersion) changelog = changelogFile.exists() ? changelogFile.getText('UTF-8') : "" versionType = getReleaseType() versionNumber = modVersion @@ -946,7 +1013,7 @@ if (modrinthApiKey.isPresent() || deploymentDebug.toBoolean()) { } String[] parts = dep.split(':') String[] qual = parts[0].split('-') - addModrinthDep(qual[0], qual[1], parts[1]) + addModrinthDep(qual[0], qual.length > 1 ? qual[1] : 'project', parts[1]) } } tasks.modrinth.dependsOn(build) @@ -955,9 +1022,17 @@ if (modrinthApiKey.isPresent() || deploymentDebug.toBoolean()) { def addModrinthDep(String scope, String type, String name) { com.modrinth.minotaur.dependencies.Dependency dep + def types = [ + 'req' : 'required', + 'opt' : 'optional', + 'embed' : 'embedded', + 'incomp': 'incompatible', 'fail': 'incompatible'] + if (types.containsKey(scope)) scope = types[scope] if (!(scope in ['required', 'optional', 'incompatible', 'embedded'])) { throw new Exception('Invalid modrinth dependency scope: ' + scope) } + types = ['proj': 'project', '': 'project', 'p': 'project', 'ver': 'version', 'v': 'version'] + if (types.containsKey(type)) type = types[type] switch (type) { case 'project': dep = new ModDependency(name, scope) diff --git a/dependencies.gradle b/dependencies.gradle index a1254cf48a9..445b5e3963b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,59 +1,71 @@ //file:noinspection DependencyNotationArgument // TODO remove when fixed in RFG ^ /* - * Add your dependencies here. Common configurations: - * - implementation("group:name:version:classifier"): if you need this for internal implementation details of the mod. - * Available at compiletime and runtime for your environment. - * - * - compileOnlyApi("g:n:v:c"): if you need this for internal implementation details of the mod. - * Available at compiletime but not runtime for your environment. - * + * Add your dependencies here. Supported configurations: + * - api("group:name:version:classifier"): if you use the types from this dependency in the public API of this mod + * Available at runtime and compiletime for mods depending on this mod + * - implementation("g:n:v:c"): if you need this for internal implementation details of the mod, but none of it is visible via the public API + * Available at runtime but not compiletime for mods depending on this mod + * - compileOnly("g:n:v:c"): if the mod you're building doesn't need this dependency during runtime at all, e.g. for optional mods + * Not available at all for mods depending on this mod, only visible at compiletime for this mod + * - compileOnlyApi("g:n:v:c"): like compileOnly, but also visible at compiletime for mods depending on this mod + * Available at compiletime but not runtime for mods depending on this mod + * - runtimeOnlyNonPublishable("g:n:v:c"): if you want to include a mod in this mod's runClient/runServer runs, but not publish it as a dependency + * Not available at all for mods depending on this mod, only visible at runtime for this mod + * - devOnlyNonPublishable("g:n:v:c"): a combination of runtimeOnlyNonPublishable and compileOnly for dependencies present at both compiletime and runtime, + * but not published as Maven dependencies - useful for RFG-deobfuscated dependencies or local testing + * - runtimeOnly("g:n:v:c"): if you don't need this at compile time, but want it to be present at runtime + * Available at runtime for mods depending on this mod * - annotationProcessor("g:n:v:c"): mostly for java compiler plugins, if you know you need this, use it, otherwise don't worry + * - testCONFIG("g:n:v:c") - replace CONFIG by one of the above (except api), same as above but for the test sources instead of main * - * - testCONFIG("g:n:v:c"): replace CONFIG by one of the above, same as above but for the test sources instead of main + * - shadowImplementation("g:n:v:c"): effectively the same as API, but the dependency is included in your jar under a renamed package name + * Requires you to enable usesShadowedDependencies in gradle.properties + * For more info, see https://github.com/GregTechCEu/Buildscripts/blob/master/docs/shadow.md * - * You can exclude transitive dependencies (dependencies of the chosen dependency) by appending { transitive = false } if needed. + * You can exclude transitive dependencies (dependencies of the chosen dependency) by appending { transitive = false } if needed, + * but use this sparingly as it can break using your mod as another mod's dependency if you're not careful. + * + * To depend on obfuscated jars you can use `devOnlyNonPublishable(rfg.deobf("dep:spec:1.2.3"))` to fetch an obfuscated jar from maven, + * or `devOnlyNonPublishable(rfg.deobf(project.files("libs/my-mod-jar.jar")))` to use a file. * * To add a mod with CurseMaven, replace '("g:n:v:c")' in the above with 'rfg.deobf("curse.maven:project_slug-project_id:file_id")' - * Example: implementation rfg.deobf("curse.maven:gregtech-ce-unofficial-557242:4527757") + * Example: devOnlyNonPublishable(rfg.deobf("curse.maven:top-245211:2667280")) + * + * Gradle names for some of the configuration can be misleading, compileOnlyApi and runtimeOnly both get published as dependencies in Maven, but compileOnly does not. + * The buildscript adds runtimeOnlyNonPublishable to also have a runtime dependency that's not published. * - * For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph + * For more details, see https://docs.gradle.org/8.4/userguide/java_library_plugin.html#sec:java_library_configurations_graph */ dependencies { - // Hard Dependencies - - // the CCL deobf jar uses very old MCP mappings, making it error at runtime in runClient/runServer - // therefore we manually deobf the regular jar - implementation rfg.deobf("curse.maven:codechicken-lib-1-8-242818:2779848") // CCL 3.2.3.358 - implementation "curse.maven:modularui-624243:4856895-deobf-4856896-sources-4856897" // MUI 2.3.1 - - // Soft Dependencies - // Can change any of these from compileOnlyApi -> implementation to test them in-game. - - implementation "CraftTweaker2:CraftTweaker2-MC1120-Main:1.12-4.1.20.684" - implementation rfg.deobf("curse.maven:ctm-267602:2915363") // CTM 1.0.2.31 - implementation ("com.cleanroommc:groovyscript:0.7.1") { - transitive = false - } - implementation rfg.deobf("curse.maven:ae2-extended-life-570458:4402048") // AE2UEL 0.55.6 + // Published dependencies + api("codechicken:codechickenlib:3.2.3.358") + api("com.cleanroommc:modularui:2.4.1") { transitive = false } + api("com.cleanroommc:groovyscript:0.8.0") { transitive = false } + api("CraftTweaker2:CraftTweaker2-MC1120-Main:1.12-4.1.20.684") + api rfg.deobf("curse.maven:ae2-extended-life-570458:4402048") // AE2UEL 0.55.6 + api rfg.deobf("curse.maven:ctm-267602:2915363") // CTM 1.0.2.31 - compileOnlyApi rfg.deobf("curse.maven:opencomputers-223008:4526246") // OpenComputers 1.8.0+9833087 - compileOnlyApi "curse.maven:journeymap-32274:2916002" // Journeymap 5.7.1 - compileOnlyApi "curse.maven:voxelmap-225179:3029445" // VoxelMap 1.9.28 - compileOnlyApi "curse.maven:xaeros-263420:4516832" // Xaero's Minimap 23.4.1 - compileOnlyApi rfg.deobf("curse.maven:hwyla-253449:2568751") // HWYLA 1.8.26-B41 - compileOnlyApi rfg.deobf("curse.maven:baubles-227083:2518667") // Baubles 1.5.2 - compileOnlyApi rfg.deobf("curse.maven:forestry-59751:2684780") // Forestry 5.8.2.387 + // Non-published dependencies + // Change any to devOnlyNonPublishable to test them in-game. + compileOnly("curse.maven:journeymap-32274:2916002") // Journeymap 5.7.1 + compileOnly("curse.maven:voxelmap-225179:3029445") // VoxelMap 1.9.28 + compileOnly("curse.maven:xaeros-263420:4516832") // Xaero's Minimap 23.4.1 + compileOnly rfg.deobf("curse.maven:opencomputers-223008:4526246") // OpenComputers 1.8.0+9833087 + compileOnly rfg.deobf("curse.maven:hwyla-253449:2568751") // HWYLA 1.8.26-B41 + compileOnly rfg.deobf("curse.maven:baubles-227083:2518667") // Baubles 1.5.2 + compileOnly rfg.deobf("curse.maven:forestry-59751:2684780") // Forestry 5.8.2.387 + compileOnly rfg.deobf("curse.maven:chisel-235279:2915375") // Chisel 1.0.2.45 // Mods with Soft compat but which have no need to be in code, such as isModLoaded() checks and getModItem() recipes. // Uncomment any of these to test them in-game. - // runtimeOnly rfg.deobf("curse.maven:beebetteratbees-244516:2627215") // BeeBetterAtBees 2.0.3 (recommended to enable when testing Forestry compat) - // runtimeOnly rfg.deobf("curse.maven:jei-bees-248370:2490058") // JEIBees 0.9.0.5 (recommended to enable when testing Forestry compat) - // runtimeOnly rfg.deobf("curse.maven:binnies-mods-223525:2916129") // Binnie 2.5.1.203 - // runtimeOnly rfg.deobf("curse.maven:magic-bees-65764:2855061") // Magic Bees 3.2.25 - // runtimeOnly rfg.deobf("curse.maven:gendustry-70492:2516215") // Gendustry 1.6.5.8 - // runtimeOnly rfg.deobf("curse.maven:bdlib-70496:2518031") // BdLib 1.14.3.12 + // runtimeOnlyNonPublishable rfg.deobf("curse.maven:beebetteratbees-244516:2627215") // BeeBetterAtBees 2.0.3 (recommended to enable when testing Forestry compat) + // runtimeOnlyNonPublishable rfg.deobf("curse.maven:jei-bees-248370:2490058") // JEIBees 0.9.0.5 (recommended to enable when testing Forestry compat) + // runtimeOnlyNonPublishable rfg.deobf("curse.maven:binnies-mods-223525:2916129") // Binnie 2.5.1.203 + // runtimeOnlyNonPublishable rfg.deobf("curse.maven:magic-bees-65764:2855061") // Magic Bees 3.2.25 + // runtimeOnlyNonPublishable rfg.deobf("curse.maven:gendustry-70492:2516215") // Gendustry 1.6.5.8 + // runtimeOnlyNonPublishable rfg.deobf("curse.maven:bdlib-70496:2518031") // BdLib 1.14.3.12 } minecraft { @@ -61,7 +73,7 @@ minecraft { } configurations { - implementation { + compileOnly { // exclude GNU trove, FastUtil is superior and still updated exclude group: "net.sf.trove4j", module: "trove4j" // exclude javax.annotation from findbugs, jetbrains annotations are superior diff --git a/gradle.properties b/gradle.properties index e213ae3b547..9de88c6bf58 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ modGroup = gregtech # Version of your mod. # This field can be left empty if you want your mod's version to be determined by the latest git tag instead. -modVersion = 2.8.3-beta +modVersion = 2.8.7-beta # Whether to use the old jar naming structure (modid-mcversion-version) instead of the new version (modid-version) includeMCVersionJar = true @@ -42,6 +42,8 @@ gradleTokenVersion = VERSION # leave this property empty. # Example value: apiPackage = api + modGroup = com.myname.mymodid -> com.myname.mymodid.api apiPackage = +# If you want to keep your API code in src/api instead of src/main +useSrcApiPath=false # Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/ # There can be multiple files in a comma-separated list. @@ -91,6 +93,9 @@ relocateShadowedDependencies = true # Useful for debugging a server and client simultaneously. If not enabled, it will be in the standard location "run/" separateRunDirectories = false +# The display name format of versions published to Curse and Modrinth. $MOD_NAME and $VERSION are available variables. +# Default: $MOD_NAME \u2212 $VERSION. \u2212 is the minus character which looks much better than the hyphen minus on Curse. +versionDisplayFormat=$MOD_NAME: $VERSION # Publishing to modrinth requires you to set the MODRINTH_API_KEY environment variable to your current modrinth API token. @@ -140,7 +145,7 @@ noPublishedSources = false # For maven credentials: # Username is set with the 'MAVEN_USER' environment variable, default to "NONE" # Password is set with the 'MAVEN_PASSWORD' environment variable, default to "NONE" -customMavenPublishUrl= +customMavenPublishUrl= https://maven.gtceu.com # The group for maven artifacts. Defaults to the 'project.modGroup' until the last '.' (if any). # So 'mymod' becomes 'mymod' and 'com.myname.mymodid' 'becomes com.myname' mavenArtifactGroup= diff --git a/settings.gradle b/settings.gradle index b6371aad5ab..771def37e1f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,9 +3,7 @@ pluginManagement { maven { // RetroFuturaGradle name 'GTNH Maven' - //noinspection HttpUrlsUsage - url 'http://jenkins.usrv.eu:8081/nexus/content/groups/public/' - allowInsecureProtocol = true + url 'https://nexus.gtnewhorizons.com/repository/public/' //noinspection GroovyAssignabilityCheck mavenContent { includeGroup 'com.gtnewhorizons' diff --git a/src/api/java/mods/railcraft/api/items/IToolCrowbar.java b/src/api/java/mods/railcraft/api/items/IToolCrowbar.java new file mode 100644 index 00000000000..7b8006b5ec2 --- /dev/null +++ b/src/api/java/mods/railcraft/api/items/IToolCrowbar.java @@ -0,0 +1,78 @@ +/*------------------------------------------------------------------------------ + Copyright (c) CovertJaguar, 2011-2020 + + This work (the API) is licensed under the "MIT" License, + see LICENSE.md for details. + -----------------------------------------------------------------------------*/ +package mods.railcraft.api.items; + +import net.minecraft.entity.item.EntityMinecart; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; + +/** + * @author CovertJaguar + */ +public interface IToolCrowbar { + String ORE_TAG = "toolCrowbar"; + + /** + * Controls non-rotational interactions with blocks. Crowbar specific stuff. + *

+ * Rotational interaction is handled by the Block.rotateBlock() function, + * which should be called from the Item.onUseFirst() function of your tool. + * + * @param player the player + * @param crowbar the crowbar + * @param pos the block @return true if can whack a block + */ + boolean canWhack(EntityPlayer player, EnumHand hand, ItemStack crowbar, BlockPos pos); + + /** + * Callback to do damage to the item. + * + * @param player the player + * @param crowbar the crowbar + * @param pos the block + */ + void onWhack(EntityPlayer player, EnumHand hand, ItemStack crowbar, BlockPos pos); + + /** + * Controls whether you can link a cart. + * + * @param player the player + * @param crowbar the crowbar + * @param cart the cart @return true if can link a cart + */ + boolean canLink(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart); + + /** + * Callback to do damage. + * + * @param player the player + * @param crowbar the crowbar + * @param cart the cart + */ + void onLink(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart); + + /** + * Controls whether you can boost a cart. + * + * @param player the player + * @param crowbar the crowbar + * @param cart the cart @return true if can boost a cart + */ + boolean canBoost(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart); + + /** + * Callback to do damage, boosting a cart usually does more damage than + * normal usage. + * + * @param player the player + * @param crowbar the crowbar + * @param cart the cart + */ + void onBoost(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart); +} diff --git a/src/api/java/mrtjp/projectred/api/IScrewdriver.java b/src/api/java/mrtjp/projectred/api/IScrewdriver.java new file mode 100644 index 00000000000..a6298fecefd --- /dev/null +++ b/src/api/java/mrtjp/projectred/api/IScrewdriver.java @@ -0,0 +1,11 @@ +package mrtjp.projectred.api; + +import net.minecraft.item.ItemStack; +import net.minecraft.entity.player.EntityPlayer; + +public interface IScrewdriver { + + boolean canUse(EntityPlayer player, ItemStack stack); + + void damageScrewdriver(EntityPlayer player, ItemStack stack); // Damage the item on usage +} diff --git a/src/main/java/gregtech/GregTechMod.java b/src/main/java/gregtech/GregTechMod.java index aa770c26ed6..f76989a97d5 100644 --- a/src/main/java/gregtech/GregTechMod.java +++ b/src/main/java/gregtech/GregTechMod.java @@ -3,9 +3,7 @@ import gregtech.api.GTValues; import gregtech.api.GregTechAPI; import gregtech.api.modules.ModuleContainerRegistryEvent; -import gregtech.api.util.oreglob.OreGlob; import gregtech.client.utils.BloomEffectUtil; -import gregtech.common.covers.filter.oreglob.impl.OreGlobParser; import gregtech.modules.GregTechModules; import gregtech.modules.ModuleManager; @@ -15,7 +13,17 @@ import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventHandler; -import net.minecraftforge.fml.common.event.*; +import net.minecraftforge.fml.common.event.FMLConstructionEvent; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLInterModComms; +import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent; +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.event.FMLServerAboutToStartEvent; +import net.minecraftforge.fml.common.event.FMLServerStartedEvent; +import net.minecraftforge.fml.common.event.FMLServerStartingEvent; +import net.minecraftforge.fml.common.event.FMLServerStoppedEvent; +import net.minecraftforge.fml.common.event.FMLServerStoppingEvent; @Mod(modid = GTValues.MODID, name = "GregTech", @@ -44,7 +52,6 @@ public GregTechMod() { public void onConstruction(FMLConstructionEvent event) { moduleManager = ModuleManager.getInstance(); GregTechAPI.moduleManager = moduleManager; - OreGlob.setCompiler(input -> new OreGlobParser(input).compile()); moduleManager.registerContainer(new GregTechModules()); MinecraftForge.EVENT_BUS.post(new ModuleContainerRegistryEvent()); moduleManager.setup(event.getASMHarvestedData(), Loader.instance().getConfigDir()); diff --git a/src/main/java/gregtech/api/GTValues.java b/src/main/java/gregtech/api/GTValues.java index fa0f424854a..05053d6fbb8 100644 --- a/src/main/java/gregtech/api/GTValues.java +++ b/src/main/java/gregtech/api/GTValues.java @@ -7,6 +7,8 @@ import net.minecraftforge.fml.relauncher.FMLLaunchHandler; import net.minecraftforge.oredict.OreDictionary; +import org.jetbrains.annotations.ApiStatus; + import java.time.LocalDate; import java.util.Random; import java.util.function.Supplier; @@ -120,10 +122,14 @@ public class GTValues { "Overpowered Voltage", "Maximum Voltage" }; /** - * ModID strings, since they are quite common parameters + * GregTech Mod ID */ - public static final String MODID = "gregtech", - MODID_FR = "forestry", + public static final String MODID = "gregtech"; + + /** @deprecated Use {@link gregtech.api.util.Mods} instead */ + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + public static final String MODID_FR = "forestry", MODID_CT = "crafttweaker", MODID_TOP = "theoneprobe", MODID_CTM = "ctm", @@ -155,7 +161,11 @@ public class GTValues { MODID_ET = "extratrees", MODID_GENETICS = "genetics", MODID_BOP = "biomesoplenty", - MODID_TCON = "tconstruct"; + MODID_TCON = "tconstruct", + MODID_PROJRED_CORE = "projectred-core", + MODID_RC = "railcraft", + MODID_CHISEL = "chisel", + MODID_RS = "refinedstorage"; private static Boolean isClient; diff --git a/src/main/java/gregtech/api/GregTechAPI.java b/src/main/java/gregtech/api/GregTechAPI.java index f3279eb5b3b..c4506dfbfc0 100644 --- a/src/main/java/gregtech/api/GregTechAPI.java +++ b/src/main/java/gregtech/api/GregTechAPI.java @@ -12,23 +12,14 @@ import gregtech.api.modules.IModuleManager; import gregtech.api.network.INetworkHandler; import gregtech.api.sound.ISoundManager; -import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.material.Material; -import gregtech.api.unification.material.Materials; import gregtech.api.unification.material.registry.IMaterialRegistryManager; import gregtech.api.unification.material.registry.MarkerMaterialRegistry; -import gregtech.api.unification.ore.OrePrefix; import gregtech.api.unification.ore.StoneType; -import gregtech.api.util.BaseCreativeTab; import gregtech.api.util.GTControlledRegistry; import gregtech.api.util.GTLog; import gregtech.api.util.IBlockOre; import gregtech.common.ConfigHolder; -import gregtech.common.blocks.BlockWarningSign; -import gregtech.common.blocks.MetaBlocks; -import gregtech.common.items.MetaItems; -import gregtech.common.items.ToolItems; -import gregtech.common.metatileentities.MetaTileEntities; import net.minecraft.block.state.IBlockState; import net.minecraft.util.ResourceLocation; @@ -78,23 +69,6 @@ public class GregTechAPI { public static final Object2ObjectMap HEATING_COILS = new Object2ObjectOpenHashMap<>(); public static final Object2ObjectMap PSS_BATTERIES = new Object2ObjectOpenHashMap<>(); - public static final BaseCreativeTab TAB_GREGTECH = new BaseCreativeTab(GTValues.MODID + ".main", - () -> MetaItems.LOGO.getStackForm(), true); - public static final BaseCreativeTab TAB_GREGTECH_MACHINES = new BaseCreativeTab(GTValues.MODID + ".machines", - () -> MetaTileEntities.ELECTRIC_BLAST_FURNACE.getStackForm(), true); - public static final BaseCreativeTab TAB_GREGTECH_CABLES = new BaseCreativeTab(GTValues.MODID + ".cables", - () -> OreDictUnifier.get(OrePrefix.cableGtDouble, Materials.Aluminium), true); - public static final BaseCreativeTab TAB_GREGTECH_PIPES = new BaseCreativeTab(GTValues.MODID + ".pipes", - () -> OreDictUnifier.get(OrePrefix.pipeNormalFluid, Materials.Aluminium), true); - public static final BaseCreativeTab TAB_GREGTECH_TOOLS = new BaseCreativeTab(GTValues.MODID + ".tools", - () -> ToolItems.HARD_HAMMER.get(Materials.Aluminium), true); - public static final BaseCreativeTab TAB_GREGTECH_MATERIALS = new BaseCreativeTab(GTValues.MODID + ".materials", - () -> OreDictUnifier.get(OrePrefix.ingot, Materials.Aluminium), true); - public static final BaseCreativeTab TAB_GREGTECH_ORES = new BaseCreativeTab(GTValues.MODID + ".ores", - () -> OreDictUnifier.get(OrePrefix.ore, Materials.Aluminium), true); - public static final BaseCreativeTab TAB_GREGTECH_DECORATIONS = new BaseCreativeTab(GTValues.MODID + ".decorations", - () -> MetaBlocks.WARNING_SIGN.getItemVariant(BlockWarningSign.SignType.YELLOW_STRIPES), true); - /** Will be available at the Pre-Initialization stage */ public static boolean isHighTier() { return highTier; diff --git a/src/main/java/gregtech/api/block/VariantActiveBlock.java b/src/main/java/gregtech/api/block/VariantActiveBlock.java index 4818c4a933a..ba4ac712a03 100644 --- a/src/main/java/gregtech/api/block/VariantActiveBlock.java +++ b/src/main/java/gregtech/api/block/VariantActiveBlock.java @@ -1,6 +1,6 @@ package gregtech.api.block; -import gregtech.api.GTValues; +import gregtech.api.util.Mods; import gregtech.client.model.ActiveVariantBlockBakedModel; import gregtech.client.utils.BloomEffectUtil; import gregtech.common.ConfigHolder; @@ -22,7 +22,6 @@ import net.minecraftforge.common.property.ExtendedBlockState; import net.minecraftforge.common.property.IExtendedBlockState; import net.minecraftforge.common.property.IUnlistedProperty; -import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -135,7 +134,7 @@ public IExtendedBlockState getExtendedState(@NotNull IBlockState state, @NotNull .withProperty(ACTIVE, Minecraft.getMinecraft().world != null && isBlockActive(Minecraft.getMinecraft().world.provider.getDimension(), pos)); - if (Loader.isModLoaded(GTValues.MODID_CTM)) { + if (Mods.CTM.isModLoaded()) { // if the Connected Textures Mod is loaded we wrap our IExtendedBlockState with their wrapper, // so that the CTM renderer can render the block properly. return new CTMExtendedState(ext, world, pos); diff --git a/src/main/java/gregtech/api/block/VariantBlock.java b/src/main/java/gregtech/api/block/VariantBlock.java index 5b0310263ef..7e31dd48c3b 100644 --- a/src/main/java/gregtech/api/block/VariantBlock.java +++ b/src/main/java/gregtech/api/block/VariantBlock.java @@ -1,7 +1,7 @@ package gregtech.api.block; -import gregtech.api.GregTechAPI; import gregtech.api.util.LocalizationUtils; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.Block; import net.minecraft.block.material.Material; @@ -43,7 +43,7 @@ public VariantBlock(Material materialIn) { state); } } - setCreativeTab(GregTechAPI.TAB_GREGTECH); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH); setDefaultState(this.blockState.getBaseState().withProperty(VARIANT, VALUES[0])); } diff --git a/src/main/java/gregtech/api/block/machines/BlockMachine.java b/src/main/java/gregtech/api/block/machines/BlockMachine.java index f9a77ccbe07..d45dfdf1e1d 100644 --- a/src/main/java/gregtech/api/block/machines/BlockMachine.java +++ b/src/main/java/gregtech/api/block/machines/BlockMachine.java @@ -1,6 +1,5 @@ package gregtech.api.block.machines; -import gregtech.api.GTValues; import gregtech.api.GregTechAPI; import gregtech.api.block.BlockCustomParticle; import gregtech.api.block.UnlistedIntegerProperty; @@ -15,7 +14,9 @@ import gregtech.api.metatileentity.multiblock.MultiblockControllerBase; import gregtech.api.pipenet.IBlockAppearance; import gregtech.api.util.GTUtility; +import gregtech.api.util.Mods; import gregtech.client.renderer.handler.MetaTileEntityRenderer; +import gregtech.common.creativetab.GTCreativeTabs; import gregtech.common.items.MetaItems; import gregtech.integration.ctm.IFacadeWrapper; @@ -40,7 +41,11 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.*; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumBlockRenderType; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; @@ -51,7 +56,6 @@ import net.minecraftforge.common.property.ExtendedBlockState; import net.minecraftforge.common.property.IExtendedBlockState; import net.minecraftforge.common.property.IUnlistedProperty; -import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -63,7 +67,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; import static gregtech.api.util.GTUtility.getMetaTileEntity; @@ -83,7 +92,7 @@ public class BlockMachine extends BlockCustomParticle implements ITileEntityProv public BlockMachine() { super(Material.IRON); - setCreativeTab(GregTechAPI.TAB_GREGTECH_MACHINES); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_MACHINES); setSoundType(SoundType.METAL); setHardness(6.0f); setResistance(6.0f); @@ -272,7 +281,7 @@ public void onBlockPlacedBy(World worldIn, @NotNull BlockPos pos, @NotNull IBloc } } } - if (Loader.isModLoaded(GTValues.MODID_APPENG)) { + if (Mods.AppliedEnergistics2.isModLoaded()) { if (metaTileEntity.getProxy() != null) { metaTileEntity.getProxy().setOwner((EntityPlayer) placer); } @@ -423,7 +432,7 @@ public void harvestBlock(@NotNull World worldIn, @NotNull EntityPlayer player, @ @NotNull IBlockState state, @Nullable TileEntity te, @NotNull ItemStack stack) { tileEntities.set(te == null ? tileEntities.get() : ((IGregTechTileEntity) te).getMetaTileEntity()); super.harvestBlock(worldIn, player, pos, state, te, stack); - tileEntities.set(null); + tileEntities.remove(); } @Nullable diff --git a/src/main/java/gregtech/api/block/machines/MachineItemBlock.java b/src/main/java/gregtech/api/block/machines/MachineItemBlock.java index 1186eaefa7c..ca0cd6417dd 100644 --- a/src/main/java/gregtech/api/block/machines/MachineItemBlock.java +++ b/src/main/java/gregtech/api/block/machines/MachineItemBlock.java @@ -10,6 +10,7 @@ import gregtech.api.util.LocalizationUtils; import gregtech.client.utils.TooltipHelper; import gregtech.common.ConfigHolder; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; @@ -61,8 +62,8 @@ public class MachineItemBlock extends ItemBlock { */ public static void addCreativeTab(CreativeTabs creativeTab) { Preconditions.checkNotNull(creativeTab, "creativeTab"); - if (creativeTab == GregTechAPI.TAB_GREGTECH_MACHINES) { - throw new IllegalArgumentException("Adding " + GregTechAPI.TAB_GREGTECH_MACHINES.tabLabel + + if (creativeTab == GTCreativeTabs.TAB_GREGTECH_MACHINES) { + throw new IllegalArgumentException("Adding " + GTCreativeTabs.TAB_GREGTECH_MACHINES.tabLabel + " as additional creative tab is redundant."); } else if (creativeTab == CreativeTabs.SEARCH) { throw new IllegalArgumentException( diff --git a/src/main/java/gregtech/api/capability/GregtechDataCodes.java b/src/main/java/gregtech/api/capability/GregtechDataCodes.java index e425d3339e4..4fcead477d4 100644 --- a/src/main/java/gregtech/api/capability/GregtechDataCodes.java +++ b/src/main/java/gregtech/api/capability/GregtechDataCodes.java @@ -166,4 +166,8 @@ public static int assignId() { // Alarm public static final int UPDATE_SOUND = assignId(); public static final int UPDATE_RADIUS = assignId(); + + // ME Parts + public static final int UPDATE_AUTO_PULL = assignId(); + public static final int UPDATE_ONLINE_STATUS = assignId(); } diff --git a/src/main/java/gregtech/api/capability/IDataStickIntractable.java b/src/main/java/gregtech/api/capability/IDataStickIntractable.java new file mode 100644 index 00000000000..4b9d36ee6ff --- /dev/null +++ b/src/main/java/gregtech/api/capability/IDataStickIntractable.java @@ -0,0 +1,11 @@ +package gregtech.api.capability; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; + +public interface IDataStickIntractable { + + void onDataStickLeftClick(EntityPlayer player, ItemStack dataStick); + + boolean onDataStickRightClick(EntityPlayer player, ItemStack dataStick); +} diff --git a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java index a241b32b3a0..6d8877ff1dc 100644 --- a/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/AbstractRecipeLogic.java @@ -1,7 +1,11 @@ package gregtech.api.capability.impl; import gregtech.api.GTValues; -import gregtech.api.capability.*; +import gregtech.api.capability.GregtechDataCodes; +import gregtech.api.capability.GregtechTileCapabilities; +import gregtech.api.capability.IMultiblockController; +import gregtech.api.capability.IMultipleTankHandler; +import gregtech.api.capability.IWorkable; import gregtech.api.metatileentity.MTETrait; import gregtech.api.metatileentity.MetaTileEntity; import gregtech.api.metatileentity.multiblock.CleanroomType; @@ -12,6 +16,7 @@ import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.logic.IParallelableRecipeLogic; import gregtech.api.recipes.recipeproperties.CleanroomProperty; +import gregtech.api.recipes.recipeproperties.DimensionProperty; import gregtech.api.recipes.recipeproperties.IRecipePropertyStorage; import gregtech.api.util.GTTransferUtils; import gregtech.api.util.GTUtility; @@ -403,7 +408,7 @@ protected boolean checkPreviousRecipe() { * @return true if the recipe is allowed to be used, else false */ public boolean checkRecipe(@NotNull Recipe recipe) { - return checkCleanroomRequirement(recipe); + return checkCleanroomRequirement(recipe) && checkDimensionRequirement(recipe); } /** @@ -427,39 +432,64 @@ protected boolean checkCleanroomRequirement(@NotNull Recipe recipe) { return false; } + protected boolean checkDimensionRequirement(@NotNull Recipe recipe) { + if (!recipe.hasProperty(DimensionProperty.getInstance())) return true; + return recipe.getProperty(DimensionProperty.getInstance(), DimensionProperty.DimensionPropertyList.EMPTY_LIST) + .checkDimension(this.getMetaTileEntity().getWorld().provider.getDimension()); + } + /** * Prepares the recipe to be run. *

    *
  1. The recipe is run in parallel if possible.
  2. *
  3. The potentially parallel recipe is then checked to exist.
  4. - *
  5. If it exists, it checks if the recipe is runnable with the current inputs.
  6. + *
  7. If it exists, it checks if the recipe is runnable with the inputs provided.
  8. *
* If the above conditions are met, the recipe is engaged to be run * - * @param recipe the recipe to prepare + * @param recipe the recipe to prepare + * @param inputInventory the inventory to draw items from + * @param inputFluidInventory the fluid tanks to draw fluid from * @return true if the recipe was successfully prepared, else false */ - protected boolean prepareRecipe(Recipe recipe) { + public boolean prepareRecipe(Recipe recipe, IItemHandlerModifiable inputInventory, + IMultipleTankHandler inputFluidInventory) { recipe = Recipe.trimRecipeOutputs(recipe, getRecipeMap(), metaTileEntity.getItemOutputLimit(), metaTileEntity.getFluidOutputLimit()); // Pass in the trimmed recipe to the parallel logic recipe = findParallelRecipe( recipe, - getInputInventory(), - getInputTank(), + inputInventory, + inputFluidInventory, getOutputInventory(), getOutputTank(), getMaxParallelVoltage(), getParallelLimit()); - if (recipe != null && setupAndConsumeRecipeInputs(recipe, getInputInventory())) { + if (recipe != null && setupAndConsumeRecipeInputs(recipe, inputInventory, inputFluidInventory)) { setupRecipe(recipe); return true; } return false; } + /** + * Prepares the recipe to be run. + *
    + *
  1. The recipe is run in parallel if possible.
  2. + *
  3. The potentially parallel recipe is then checked to exist.
  4. + *
  5. If it exists, it checks if the recipe is runnable with the current inputs.
  6. + *
+ * If the above conditions are met, the recipe is engaged to be run + * + * @param recipe the recipe to prepare + * @return true if the recipe was successfully prepared from the default inventory, else false + */ + public boolean prepareRecipe(Recipe recipe) { + return prepareRecipe(recipe, getInputInventory(), getInputTank()); + } + /** * DO NOT use the parallelLimit field directly, EVER * @@ -549,11 +579,14 @@ protected static boolean areItemStacksEqual(@NotNull ItemStack stackA, @NotNull * @param recipe - The Recipe that will be consumed from the inputs and ran in the machine * @param importInventory - The inventory that the recipe should be consumed from. * Used mainly for Distinct bus implementation for multiblocks to specify - * a specific bus + * a specific bus, or for addons to use external inventories. + * @param importFluids - The tanks that the recipe should be consumed from + * Used currently in addons to use external tanks. * @return - true if the recipe is successful, false if the recipe is not successful */ protected boolean setupAndConsumeRecipeInputs(@NotNull Recipe recipe, - @NotNull IItemHandlerModifiable importInventory) { + @NotNull IItemHandlerModifiable importInventory, + @NotNull IMultipleTankHandler importFluids) { this.overclockResults = calculateOverclock(recipe); modifyOverclockPost(overclockResults, recipe.getRecipePropertyStorage()); @@ -563,7 +596,6 @@ protected boolean setupAndConsumeRecipeInputs(@NotNull Recipe recipe, } IItemHandlerModifiable exportInventory = getOutputInventory(); - IMultipleTankHandler importFluids = getInputTank(); IMultipleTankHandler exportFluids = getOutputTank(); // We have already trimmed outputs and chanced outputs at this time @@ -589,37 +621,42 @@ protected boolean setupAndConsumeRecipeInputs(@NotNull Recipe recipe, return false; } + /** + * Determines if the provided recipe is possible to run from the provided inventory, or if there is anything + * preventing + * the Recipe from being completed. + *

+ * Will consume the inputs of the Recipe if it is possible to run. + * + * @param recipe - The Recipe that will be consumed from the inputs and ran in the machine + * @param importInventory - The inventory that the recipe should be consumed from. + * Used mainly for Distinct bus implementation for multiblocks to specify + * a specific bus + * @return - true if the recipe is successful, false if the recipe is not successful + */ + protected boolean setupAndConsumeRecipeInputs(@NotNull Recipe recipe, + @NotNull IItemHandlerModifiable importInventory) { + return setupAndConsumeRecipeInputs(recipe, importInventory, this.getInputTank()); + } + /** * @param resultOverclock the overclock data to use. Format: {@code [EUt, duration]}. * @return true if there is enough energy to continue recipe progress */ - protected boolean hasEnoughPower(@NotNull int[] resultOverclock) { + protected boolean hasEnoughPower(int @NotNull [] resultOverclock) { // Format of resultOverclock: EU/t, duration - int totalEUt = resultOverclock[0] * resultOverclock[1]; + int recipeEUt = resultOverclock[0]; // RIP Ternary // Power Consumption case - if (totalEUt >= 0) { - int capacity; - // If the total consumed power is greater than half the internal capacity - if (totalEUt > getEnergyCapacity() / 2) { - // Only draw 1A of power from the internal buffer to allow for recharging of the internal buffer from - // external sources - capacity = resultOverclock[0]; - } else { - // If the total consumed power is less than half the capacity, just drain the whole thing - capacity = totalEUt; - } - - // Return true if we have enough energy stored to progress the recipe, either 1A or the whole amount - return getEnergyStored() >= capacity; + if (recipeEUt >= 0) { + // ensure it can run for at least 8 ticks. Arbitrary value, but should prevent instant failures + return getEnergyStored() >= ((long) recipeEUt << 3); } // Power Generation case else { - // This is the EU/t generated by the generator - int power = resultOverclock[0]; // Return true if we can fit at least 1A of energy into the energy output - return getEnergyStored() - (long) power <= getEnergyCapacity(); + return getEnergyStored() - (long) recipeEUt <= getEnergyCapacity(); } } @@ -651,8 +688,7 @@ protected int[] calculateOverclock(@NotNull Recipe recipe) { * @param recipe the recipe to overclock * @return an int array of {OverclockedEUt, OverclockedDuration} */ - @NotNull - protected int[] performOverclocking(@NotNull Recipe recipe) { + protected int @NotNull [] performOverclocking(@NotNull Recipe recipe) { int[] values = { recipe.getEUt(), recipe.getDuration(), getNumberOfOCs(recipe.getEUt()) }; modifyOverclockPre(values, recipe.getRecipePropertyStorage()); diff --git a/src/main/java/gregtech/api/capability/impl/EnergyContainerHandler.java b/src/main/java/gregtech/api/capability/impl/EnergyContainerHandler.java index 81972c3f5d7..37688a4deec 100644 --- a/src/main/java/gregtech/api/capability/impl/EnergyContainerHandler.java +++ b/src/main/java/gregtech/api/capability/impl/EnergyContainerHandler.java @@ -157,16 +157,16 @@ private boolean handleElectricItem(IElectricItem electricItem) { // Check if the item is a battery (or similar), and if we can receive some amount of energy if (electricItem.canProvideChargeExternally() && getEnergyCanBeInserted() > 0) { - // Drain from the battery if we are below half energy capacity, and if the tier matches - if (chargePercent <= 0.5 && chargeTier == machineTier) { + // Drain from the battery if we are below 1/3rd energy capacity, and if the tier matches + if (chargePercent <= 0.33 && chargeTier == machineTier) { long dischargedBy = electricItem.discharge(getEnergyCanBeInserted(), machineTier, false, true, false); addEnergy(dischargedBy); return dischargedBy > 0L; } } - // Else, check if we have above 50% power - if (chargePercent > 0.5) { + // Else, check if we have above 2/3rds charge + if (chargePercent > 0.66) { long chargedBy = electricItem.charge(getEnergyStored(), chargeTier, false, false); removeEnergy(chargedBy); return chargedBy > 0; @@ -178,7 +178,7 @@ private boolean handleForgeEnergyItem(IEnergyStorage energyStorage) { int machineTier = GTUtility.getTierByVoltage(Math.max(getInputVoltage(), getOutputVoltage())); double chargePercent = getEnergyStored() / (getEnergyCapacity() * 1.0); - if (chargePercent > 0.5) { + if (chargePercent > 0.66) { // 2/3rds full long chargedBy = FeCompat.insertEu(energyStorage, GTValues.V[machineTier]); removeEnergy(chargedBy); return chargedBy > 0; diff --git a/src/main/java/gregtech/api/capability/impl/MultiblockFuelRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/MultiblockFuelRecipeLogic.java index 9f93440025c..168ed40c67c 100644 --- a/src/main/java/gregtech/api/capability/impl/MultiblockFuelRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/MultiblockFuelRecipeLogic.java @@ -78,6 +78,11 @@ public int getParallelLimit() { return Integer.MAX_VALUE; } + @Override + protected long getMaxParallelVoltage() { + return getMaxVoltage(); + } + /** * Boost the energy production. * Should not change the state of the workable logic. Only read current values. diff --git a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java index 3917d4a7bda..b27685c6987 100644 --- a/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/MultiblockRecipeLogic.java @@ -282,7 +282,7 @@ protected boolean prepareRecipeDistinct(Recipe recipe) { } @Override - protected void modifyOverclockPre(@NotNull int[] values, @NotNull IRecipePropertyStorage storage) { + protected void modifyOverclockPre(int @NotNull [] values, @NotNull IRecipePropertyStorage storage) { super.modifyOverclockPre(values, storage); // apply maintenance bonuses @@ -434,6 +434,11 @@ public long getMaxVoltage() { } } + @Override + protected long getMaxParallelVoltage() { + return getMaximumOverclockVoltage(); + } + @Nullable @Override public RecipeMap getRecipeMap() { diff --git a/src/main/java/gregtech/api/capability/impl/PrimitiveRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/PrimitiveRecipeLogic.java index 182b900ae3b..e3367929b06 100644 --- a/src/main/java/gregtech/api/capability/impl/PrimitiveRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/PrimitiveRecipeLogic.java @@ -38,15 +38,19 @@ protected boolean drawEnergy(int recipeEUt, boolean simulate) { return true; // spoof energy being drawn } + @Override + protected boolean hasEnoughPower(int @NotNull [] resultOverclock) { + return true; + } + @Override public long getMaxVoltage() { return GTValues.LV; } - @NotNull @Override - protected int[] runOverclockingLogic(@NotNull IRecipePropertyStorage propertyStorage, int recipeEUt, - long maxVoltage, int recipeDuration, int amountOC) { + protected int @NotNull [] runOverclockingLogic(@NotNull IRecipePropertyStorage propertyStorage, int recipeEUt, + long maxVoltage, int recipeDuration, int amountOC) { return standardOverclockingLogic( 1, getMaxVoltage(), diff --git a/src/main/java/gregtech/api/capability/impl/RecipeLogicSteam.java b/src/main/java/gregtech/api/capability/impl/RecipeLogicSteam.java index c5d7dcbb960..c1e4fc665a9 100644 --- a/src/main/java/gregtech/api/capability/impl/RecipeLogicSteam.java +++ b/src/main/java/gregtech/api/capability/impl/RecipeLogicSteam.java @@ -227,6 +227,23 @@ public long getMaxVoltage() { return GTValues.V[GTValues.LV]; } + @Override + protected boolean hasEnoughPower(int @NotNull [] resultOverclock) { + int totalSteam = (int) (resultOverclock[0] * resultOverclock[1] / conversionRate); + if (totalSteam > 0) { + long steamStored = getEnergyStored(); + long steamCapacity = getEnergyCapacity(); + // if the required steam is larger than the full buffer, just require the full buffer + if (steamCapacity < totalSteam) { + return steamCapacity == steamStored; + } + // otherwise require the full amount of steam for the recipe + return steamStored >= totalSteam; + } + // generation case unchanged + return super.hasEnoughPower(resultOverclock); + } + @NotNull @Override public NBTTagCompound serializeNBT() { diff --git a/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java index b227f3942c2..d649a7d6fa7 100644 --- a/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java +++ b/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java @@ -167,4 +167,21 @@ private void performVentingAnimation(BlockPos machinePos, EnumFacing ventingSide 1.0f); } } + + @Override + protected boolean hasEnoughPower(int @NotNull [] resultOverclock) { + int totalSteam = (int) (resultOverclock[0] * resultOverclock[1] / conversionRate); + if (totalSteam > 0) { + long steamStored = getEnergyStored(); + long steamCapacity = getEnergyCapacity(); + // if the required steam is larger than the full buffer, just require the full buffer + if (steamCapacity < totalSteam) { + return steamCapacity == steamStored; + } + // otherwise require the full amount of steam for the recipe + return steamStored >= totalSteam; + } + // generation case unchanged + return super.hasEnoughPower(resultOverclock); + } } diff --git a/src/main/java/gregtech/api/cover/Cover.java b/src/main/java/gregtech/api/cover/Cover.java index 8540d97fc1f..0257544c99a 100644 --- a/src/main/java/gregtech/api/cover/Cover.java +++ b/src/main/java/gregtech/api/cover/Cover.java @@ -113,10 +113,13 @@ default long getOffsetTimer() { /** * Called when the cover is first attached on the Server Side. - * Do NOT sync custom data to client here. It will overwrite the attach cover packet! + * Values set here will automatically be synced to the client, if you + * specify them in {@link #writeInitialSyncData}. + * + * @apiNote The CoverableView will not have your cover attached to it in this method. * - * @param coverableView the CoverableView this cover is attached to - * @param side the side this cover is attached to + * @param coverableView the CoverableView this cover will be attached to + * @param side the side this cover will be attached to * @param player the player attaching the cover * @param itemStack the item used to place the cover */ diff --git a/src/main/java/gregtech/api/cover/CoverWithUI.java b/src/main/java/gregtech/api/cover/CoverWithUI.java index a504a7484d0..f40f2df37bc 100644 --- a/src/main/java/gregtech/api/cover/CoverWithUI.java +++ b/src/main/java/gregtech/api/cover/CoverWithUI.java @@ -3,22 +3,30 @@ import gregtech.api.gui.IUIHolder; import gregtech.api.gui.ModularUI; import gregtech.api.mui.GTGuiTheme; -import gregtech.api.mui.GTGuis; import gregtech.api.mui.GregTechGuiScreen; +import gregtech.api.mui.factory.CoverGuiFactory; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import com.cleanroommc.modularui.api.IGuiHolder; -import com.cleanroommc.modularui.manager.GuiCreationContext; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.drawable.ItemDrawable; +import com.cleanroommc.modularui.factory.SidedPosGuiData; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; +import com.cleanroommc.modularui.value.BoolValue; +import com.cleanroommc.modularui.value.sync.EnumSyncValue; import com.cleanroommc.modularui.value.sync.GuiSyncManager; +import com.cleanroommc.modularui.value.sync.IntSyncValue; +import com.cleanroommc.modularui.widget.ParentWidget; +import com.cleanroommc.modularui.widgets.layout.Row; import org.jetbrains.annotations.ApiStatus; -public interface CoverWithUI extends Cover, IUIHolder, IGuiHolder { +public interface CoverWithUI extends Cover, IUIHolder, IGuiHolder { @ApiStatus.Experimental default boolean usesMui2() { @@ -27,8 +35,7 @@ default boolean usesMui2() { default void openUI(EntityPlayerMP player) { if (usesMui2()) { - GTGuis.getCoverUiInfo(getAttachedSide()) - .open(player, getCoverableView().getWorld(), getCoverableView().getPos()); + CoverGuiFactory.open(player, this); } else { CoverUIFactory.INSTANCE.openUI(this, player); } @@ -42,17 +49,16 @@ default ModularUI createUI(EntityPlayer player) { @ApiStatus.NonExtendable @SideOnly(Side.CLIENT) @Override - default ModularScreen createScreen(GuiCreationContext creationContext, ModularPanel mainPanel) { + default ModularScreen createScreen(SidedPosGuiData guiData, ModularPanel mainPanel) { return new GregTechGuiScreen(mainPanel, getUITheme()); } default GTGuiTheme getUITheme() { - return GTGuiTheme.STANDARD; + return GTGuiTheme.COVER; } @Override - default ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManager guiSyncManager, - boolean isClient) { + default ModularPanel buildUI(SidedPosGuiData guiData, GuiSyncManager guiSyncManager) { return null; } @@ -70,4 +76,51 @@ default boolean isRemote() { default void markAsDirty() { getCoverableView().markDirty(); } + + /* Helper methods for UI creation with covers that are commonly used */ + + /** + * The color used for Cover UI titles, and used in {@link #createTitleRow}. + */ + int UI_TITLE_COLOR = 0xFF222222; + /** + * The color used for Cover UI text. Available for reference, but is + * handled automatically by the {@link GTGuiTheme#COVER} theme. + */ + int UI_TEXT_COLOR = 0xFF555555; + + /** + * Create the Title bar widget for a Cover. + */ + default Row createTitleRow() { + ItemStack item = getDefinition().getDropItemStack(); + return new Row() + .pos(4, 4) + .height(16).coverChildrenWidth() + .child(new ItemDrawable(getDefinition().getDropItemStack()).asWidget().size(16).marginRight(4)) + .child(IKey.str(item.getDisplayName()).color(UI_TITLE_COLOR).asWidget().heightRel(1.0f)); + } + + /** + * Create a new settings row for a Cover setting. + */ + default ParentWidget createSettingsRow() { + return new ParentWidget<>().height(16).widthRel(1.0f).marginBottom(2); + } + + /** + * Get a BoolValue for use with toggle buttons which are "linked together," + * meaning only one of them can be pressed at a time. + */ + default > BoolValue.Dynamic boolValueOf(EnumSyncValue syncValue, T value) { + return new BoolValue.Dynamic(() -> syncValue.getValue() == value, $ -> syncValue.setValue(value)); + } + + /** + * Get a BoolValue for use with toggle buttons which are "linked together," + * meaning only one of them can be pressed at a time. + */ + default BoolValue.Dynamic boolValueOf(IntSyncValue syncValue, int value) { + return new BoolValue.Dynamic(() -> syncValue.getValue() == value, $ -> syncValue.setValue(value)); + } } diff --git a/src/main/java/gregtech/api/cover/CoverableView.java b/src/main/java/gregtech/api/cover/CoverableView.java index 0b7d6f33c68..8bebd97b26d 100644 --- a/src/main/java/gregtech/api/cover/CoverableView.java +++ b/src/main/java/gregtech/api/cover/CoverableView.java @@ -1,5 +1,6 @@ package gregtech.api.cover; +import net.minecraft.item.ItemStack; import net.minecraft.network.PacketBuffer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.EnumFacing; @@ -87,4 +88,10 @@ default boolean hasCover(@NotNull EnumFacing side) { int getInputRedstoneSignal(@NotNull EnumFacing side, boolean ignoreCover); void writeCoverData(@NotNull Cover cover, int discriminator, @NotNull Consumer<@NotNull PacketBuffer> buf); + + /** + * @return an ItemStack representation of the CoverableView, or {@link ItemStack#EMPTY} if not possible. + */ + @NotNull + ItemStack getStackForm(); } diff --git a/src/main/java/gregtech/api/util/BaseCreativeTab.java b/src/main/java/gregtech/api/creativetab/BaseCreativeTab.java similarity index 78% rename from src/main/java/gregtech/api/util/BaseCreativeTab.java rename to src/main/java/gregtech/api/creativetab/BaseCreativeTab.java index 0b9fd926651..4607cf05d08 100644 --- a/src/main/java/gregtech/api/util/BaseCreativeTab.java +++ b/src/main/java/gregtech/api/creativetab/BaseCreativeTab.java @@ -1,4 +1,6 @@ -package gregtech.api.util; +package gregtech.api.creativetab; + +import gregtech.api.util.GTLog; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.init.Blocks; @@ -13,31 +15,32 @@ public class BaseCreativeTab extends CreativeTabs { private final boolean hasSearchBar; private final Supplier iconSupplier; - public BaseCreativeTab(String TabName, Supplier iconSupplier, boolean hasSearchBar) { - super(TabName); + public BaseCreativeTab(String tabName, Supplier iconSupplier, boolean hasSearchBar) { + super(tabName); this.iconSupplier = iconSupplier; this.hasSearchBar = hasSearchBar; - if (hasSearchBar) + if (hasSearchBar) { setBackgroundImageName("item_search.png"); + } } @NotNull @Override public ItemStack createIcon() { if (iconSupplier == null) { - GTLog.logger.error("Icon supplier was null for CreativeTab " + getTabLabel()); + GTLog.logger.error("Icon supplier was null for CreativeTab {}", getTabLabel()); return new ItemStack(Blocks.STONE); } ItemStack stack = iconSupplier.get(); if (stack == null) { - GTLog.logger.error("Icon supplier return null for CreativeTab " + getTabLabel()); + GTLog.logger.error("Icon supplier return null for CreativeTab {}", getTabLabel()); return new ItemStack(Blocks.STONE); } - if (stack == ItemStack.EMPTY) { - GTLog.logger.error("Icon built from iconSupplied is EMPTY for CreativeTab " + getTabLabel()); + if (stack.isEmpty()) { + GTLog.logger.error("Icon built from iconSupplied is EMPTY for CreativeTab {}", getTabLabel()); return new ItemStack(Blocks.STONE); } diff --git a/src/main/java/gregtech/api/fluids/FluidBuilder.java b/src/main/java/gregtech/api/fluids/FluidBuilder.java index 757369b75ae..a7c9d6528ef 100644 --- a/src/main/java/gregtech/api/fluids/FluidBuilder.java +++ b/src/main/java/gregtech/api/fluids/FluidBuilder.java @@ -1,6 +1,5 @@ package gregtech.api.fluids; -import gregtech.api.GTValues; import gregtech.api.fluids.attribute.AttributedFluid; import gregtech.api.fluids.attribute.FluidAttribute; import gregtech.api.fluids.store.FluidStorageKey; @@ -12,13 +11,13 @@ import gregtech.api.util.FluidTooltipUtil; import gregtech.api.util.GTLog; import gregtech.api.util.GTUtility; +import gregtech.api.util.Mods; import net.minecraft.block.material.MaterialLiquid; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fluids.BlockFluidBase; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidRegistry; -import net.minecraftforge.fml.common.Loader; import com.google.common.base.Preconditions; import io.github.drmanganese.topaddons.reference.Colors; @@ -380,7 +379,7 @@ private static int convertViscosity(double viscosity) { } // register cross mod compat for colors - if (Loader.isModLoaded(GTValues.MODID_TOP_ADDONS)) { + if (Mods.TOPAddons.isModLoaded()) { int displayColor = isColorEnabled || material == null ? color : material.getMaterialRGB(); Colors.FLUID_NAME_COLOR_MAP.put(name, displayColor); } @@ -428,7 +427,7 @@ private void determineTemperature(@Nullable Material material) { } case GAS -> ROOM_TEMPERATURE; case PLASMA -> { - if (material.hasFluid()) { + if (material.hasFluid() && material.getFluid() != null) { yield BASE_PLASMA_TEMPERATURE + material.getFluid().getTemperature(); } yield BASE_PLASMA_TEMPERATURE; diff --git a/src/main/java/gregtech/api/fluids/store/FluidStorage.java b/src/main/java/gregtech/api/fluids/store/FluidStorage.java index f9a232b0d27..fe7505423d0 100644 --- a/src/main/java/gregtech/api/fluids/store/FluidStorage.java +++ b/src/main/java/gregtech/api/fluids/store/FluidStorage.java @@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Comparator; import java.util.Map; public final class FluidStorage { @@ -69,12 +70,14 @@ public void registerFluids(@NotNull Material material) { enqueueRegistration(FluidStorageKeys.LIQUID, new FluidBuilder()); } - for (var entry : toRegister.entrySet()) { - Fluid fluid = entry.getValue().build(material.getModid(), material, entry.getKey()); - if (!storeNoOverwrites(entry.getKey(), fluid)) { - GTLog.logger.error("{} already has an associated fluid for material {}", material); - } - } + toRegister.entrySet().stream() + .sorted(Comparator.comparingInt(e -> -e.getKey().getRegistrationPriority())) + .forEach(entry -> { + Fluid fluid = entry.getValue().build(material.getModid(), material, entry.getKey()); + if (!storeNoOverwrites(entry.getKey(), fluid)) { + GTLog.logger.error("{} already has an associated fluid for material {}", material); + } + }); toRegister = null; registered = true; } diff --git a/src/main/java/gregtech/api/fluids/store/FluidStorageKey.java b/src/main/java/gregtech/api/fluids/store/FluidStorageKey.java index 01f44dcc8da..7b58514aa35 100644 --- a/src/main/java/gregtech/api/fluids/store/FluidStorageKey.java +++ b/src/main/java/gregtech/api/fluids/store/FluidStorageKey.java @@ -24,6 +24,7 @@ public final class FluidStorageKey { private final Function translationKeyFunction; private final int hashCode; private final FluidState defaultFluidState; + private final int registrationPriority; public FluidStorageKey(@NotNull ResourceLocation resourceLocation, @NotNull MaterialIconType iconType, @NotNull UnaryOperator<@NotNull String> registryNameOperator, @@ -35,12 +36,20 @@ public FluidStorageKey(@NotNull ResourceLocation resourceLocation, @NotNull Mate @NotNull UnaryOperator<@NotNull String> registryNameOperator, @NotNull Function<@NotNull Material, @NotNull String> translationKeyFunction, @Nullable FluidState defaultFluidState) { + this(resourceLocation, iconType, registryNameOperator, translationKeyFunction, defaultFluidState, 0); + } + + public FluidStorageKey(@NotNull ResourceLocation resourceLocation, @NotNull MaterialIconType iconType, + @NotNull UnaryOperator<@NotNull String> registryNameOperator, + @NotNull Function<@NotNull Material, @NotNull String> translationKeyFunction, + @Nullable FluidState defaultFluidState, int registrationPriority) { this.resourceLocation = resourceLocation; this.iconType = iconType; this.registryNameOperator = registryNameOperator; this.translationKeyFunction = translationKeyFunction; this.hashCode = resourceLocation.hashCode(); this.defaultFluidState = defaultFluidState; + this.registrationPriority = registrationPriority; if (keys.containsKey(resourceLocation)) { throw new IllegalArgumentException("Cannot create duplicate keys"); } @@ -81,6 +90,14 @@ public FluidStorageKey(@NotNull ResourceLocation resourceLocation, @NotNull Mate return defaultFluidState; } + /** + * @return The registration priority for this fluid type, determining the build order for fluids. + * Useful for when your fluid building requires some properties from previous fluids. + */ + public int getRegistrationPriority() { + return registrationPriority; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/gregtech/api/fluids/store/FluidStorageKeys.java b/src/main/java/gregtech/api/fluids/store/FluidStorageKeys.java index 10407e03b03..3b9a6be51e8 100644 --- a/src/main/java/gregtech/api/fluids/store/FluidStorageKeys.java +++ b/src/main/java/gregtech/api/fluids/store/FluidStorageKeys.java @@ -33,7 +33,7 @@ public final class FluidStorageKeys { public static final FluidStorageKey PLASMA = new FluidStorageKey(gregtechId("plasma"), MaterialIconType.plasma, s -> "plasma." + s, m -> "gregtech.fluid.plasma", - FluidState.PLASMA); + FluidState.PLASMA, -1); private FluidStorageKeys() {} } diff --git a/src/main/java/gregtech/api/gui/GuiTextures.java b/src/main/java/gregtech/api/gui/GuiTextures.java index fb073716142..e2b7afa929a 100644 --- a/src/main/java/gregtech/api/gui/GuiTextures.java +++ b/src/main/java/gregtech/api/gui/GuiTextures.java @@ -48,6 +48,9 @@ public class GuiTextures { public static final TextureArea FLUID_TANK_OVERLAY = TextureArea .fullImage("textures/gui/base/fluid_tank_overlay.png"); public static final TextureArea SLOT = AdoptableTextureArea.fullImage("textures/gui/base/slot.png", 18, 18, 1, 1); + public static final TextureArea SLOT_DARK = AdoptableTextureArea.fullImage("textures/gui/base/slot_dark.png", 18, + 18, 1, 1); + @Deprecated // idek what this texture is public static final TextureArea SLOT_DARKENED = TextureArea.fullImage("textures/gui/base/darkened_slot.png"); public static final SteamTexture SLOT_STEAM = SteamTexture.fullImage("textures/gui/base/slot_%s.png"); public static final TextureArea TOGGLE_BUTTON_BACK = TextureArea @@ -435,6 +438,10 @@ public class GuiTextures { .fullImage("textures/items/metaitems/cover.controller.png"); // Ore Filter + public static final TextureArea ORE_FILTER_BUTTON_CASE_SENSITIVE = TextureArea + .fullImage("textures/gui/widget/ore_filter/button_case_sensitive.png"); + public static final TextureArea ORE_FILTER_BUTTON_MATCH_ALL = TextureArea + .fullImage("textures/gui/widget/ore_filter/button_match_all.png"); public static final TextureArea ORE_FILTER_INFO = TextureArea.fullImage("textures/gui/widget/ore_filter/info.png"); public static final TextureArea ORE_FILTER_SUCCESS = TextureArea .fullImage("textures/gui/widget/ore_filter/success.png"); @@ -500,6 +507,9 @@ public class GuiTextures { public static final TextureArea CONFIG_ARROW_DARK = TextureArea .fullImage("textures/gui/widget/config_arrow_dark.png"); public static final TextureArea SELECT_BOX = TextureArea.fullImage("textures/gui/widget/select_box.png"); + public static final TextureArea BUTTON_AUTO_PULL = TextureArea + .fullImage("textures/gui/widget/button_me_auto_pull.png"); + public static final TextureArea ARROW_DOUBLE = TextureArea.fullImage("textures/gui/widget/arrow_double.png"); // Fusion Reactor custom images public static final TextureArea FUSION_REACTOR_MK1_TITLE = TextureArea diff --git a/src/main/java/gregtech/api/gui/widgets/SortingButtonWidget.java b/src/main/java/gregtech/api/gui/widgets/SortingButtonWidget.java index d053c49e7d1..2c491d18ecc 100644 --- a/src/main/java/gregtech/api/gui/widgets/SortingButtonWidget.java +++ b/src/main/java/gregtech/api/gui/widgets/SortingButtonWidget.java @@ -1,14 +1,13 @@ package gregtech.api.gui.widgets; +import gregtech.api.util.Mods; + import net.minecraft.client.settings.KeyBinding; -import net.minecraftforge.fml.common.Loader; import java.util.function.Consumer; public class SortingButtonWidget extends ClickButtonWidget { - private static boolean inventoryTweaksChecked; - private static boolean inventoryTweaksPresent; private static KeyBinding sortKeyBinding; public SortingButtonWidget(int xPosition, int yPosition, int width, int height, String displayText, @@ -43,13 +42,10 @@ public boolean keyTyped(char charTyped, int keyCode) { } private static int getInvTweaksSortCode() { - if (!inventoryTweaksChecked) { - inventoryTweaksChecked = true; - inventoryTweaksPresent = Loader.isModLoaded("inventorytweaks"); - } - if (!inventoryTweaksPresent) { + if (!Mods.InventoryTweaks.isModLoaded()) { return 0; } + try { if (sortKeyBinding == null) { Class proxyClass = Class.forName("invtweaks.forge.ClientProxy"); diff --git a/src/main/java/gregtech/api/items/armor/ArmorMetaItem.java b/src/main/java/gregtech/api/items/armor/ArmorMetaItem.java index e7e663377d0..a972b669c95 100644 --- a/src/main/java/gregtech/api/items/armor/ArmorMetaItem.java +++ b/src/main/java/gregtech/api/items/armor/ArmorMetaItem.java @@ -1,9 +1,9 @@ package gregtech.api.items.armor; -import gregtech.api.GregTechAPI; import gregtech.api.items.metaitem.MetaItem; import gregtech.api.items.metaitem.stats.IEnchantabilityHelper; import gregtech.api.items.metaitem.stats.IItemComponent; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.model.ModelBiped; @@ -33,7 +33,7 @@ public class ArmorMetaItem.ArmorMetaValueItem> extend public ArmorMetaItem() { super((short) 0); - setCreativeTab(GregTechAPI.TAB_GREGTECH_TOOLS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_TOOLS); } @SuppressWarnings("unchecked") diff --git a/src/main/java/gregtech/api/items/behavior/CoverItemBehavior.java b/src/main/java/gregtech/api/items/behavior/CoverItemBehavior.java index 5feda5eac58..826e85d961d 100644 --- a/src/main/java/gregtech/api/items/behavior/CoverItemBehavior.java +++ b/src/main/java/gregtech/api/items/behavior/CoverItemBehavior.java @@ -56,8 +56,10 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, @NotNull World world ItemStack itemStack = player.getHeldItem(hand); - coverHolder.addCover(coverSide, cover); + // Call onAttachment first so that the cover can set up any + // necessary variables needed for the S2C cover sync packet. cover.onAttachment(coverHolder, coverSide, player, itemStack); + coverHolder.addCover(coverSide, cover); AdvancementTriggers.FIRST_COVER_PLACE.trigger((EntityPlayerMP) player); diff --git a/src/main/java/gregtech/api/items/gui/ItemUIFactory.java b/src/main/java/gregtech/api/items/gui/ItemUIFactory.java index 998bca5dec7..abff0448370 100644 --- a/src/main/java/gregtech/api/items/gui/ItemUIFactory.java +++ b/src/main/java/gregtech/api/items/gui/ItemUIFactory.java @@ -10,13 +10,13 @@ import net.minecraftforge.fml.relauncher.SideOnly; import com.cleanroommc.modularui.api.IGuiHolder; -import com.cleanroommc.modularui.manager.GuiCreationContext; +import com.cleanroommc.modularui.factory.HandGuiData; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.value.sync.GuiSyncManager; import org.jetbrains.annotations.ApiStatus; -public interface ItemUIFactory extends IItemComponent, IGuiHolder { +public interface ItemUIFactory extends IItemComponent, IGuiHolder { /** * Creates new UI basing on given holder. Holder contains information @@ -30,7 +30,7 @@ default ModularUI createUI(PlayerInventoryHolder holder, EntityPlayer entityPlay @ApiStatus.NonExtendable @SideOnly(Side.CLIENT) @Override - default ModularScreen createScreen(GuiCreationContext creationContext, ModularPanel mainPanel) { + default ModularScreen createScreen(HandGuiData creationContext, ModularPanel mainPanel) { return new GregTechGuiScreen(mainPanel, getUITheme()); } @@ -39,8 +39,7 @@ default GTGuiTheme getUITheme() { } @Override - default ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManager guiSyncManager, - boolean isClient) { + default ModularPanel buildUI(HandGuiData guiData, GuiSyncManager guiSyncManager) { return null; } } diff --git a/src/main/java/gregtech/api/items/materialitem/MetaPrefixItem.java b/src/main/java/gregtech/api/items/materialitem/MetaPrefixItem.java index 35172727add..614e5fa1076 100644 --- a/src/main/java/gregtech/api/items/materialitem/MetaPrefixItem.java +++ b/src/main/java/gregtech/api/items/materialitem/MetaPrefixItem.java @@ -1,7 +1,6 @@ package gregtech.api.items.materialitem; import gregtech.api.GTValues; -import gregtech.api.GregTechAPI; import gregtech.api.damagesources.DamageSources; import gregtech.api.items.armor.ArmorMetaItem; import gregtech.api.items.metaitem.StandardMetaItem; @@ -15,6 +14,7 @@ import gregtech.api.unification.material.registry.MaterialRegistry; import gregtech.api.unification.ore.OrePrefix; import gregtech.api.unification.stack.UnificationEntry; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.BlockCauldron; import net.minecraft.block.state.IBlockState; @@ -58,7 +58,7 @@ public MetaPrefixItem(@NotNull MaterialRegistry registry, @NotNull OrePrefix ore super(); this.registry = registry; this.prefix = orePrefix; - this.setCreativeTab(GregTechAPI.TAB_GREGTECH_MATERIALS); + this.setCreativeTab(GTCreativeTabs.TAB_GREGTECH_MATERIALS); } @Override diff --git a/src/main/java/gregtech/api/items/metaitem/ElectricStats.java b/src/main/java/gregtech/api/items/metaitem/ElectricStats.java index 91bd1b739da..bf336ec4676 100644 --- a/src/main/java/gregtech/api/items/metaitem/ElectricStats.java +++ b/src/main/java/gregtech/api/items/metaitem/ElectricStats.java @@ -6,6 +6,7 @@ import gregtech.api.capability.IElectricItem; import gregtech.api.capability.impl.ElectricItem; import gregtech.api.items.metaitem.stats.*; +import gregtech.api.util.Mods; import gregtech.common.ConfigHolder; import gregtech.integration.baubles.BaublesModule; @@ -25,7 +26,6 @@ import net.minecraftforge.common.capabilities.ICapabilityProvider; import net.minecraftforge.energy.CapabilityEnergy; import net.minecraftforge.energy.IEnergyStorage; -import net.minecraftforge.fml.common.Loader; import java.time.Duration; import java.time.Instant; @@ -75,7 +75,7 @@ public void onUpdate(ItemStack itemStack, Entity entity) { IInventory inventoryPlayer = entityPlayer.inventory; long transferLimit = electricItem.getTransferLimit(); - if (Loader.isModLoaded(GTValues.MODID_BAUBLES)) { + if (Mods.Baubles.isModLoaded()) { inventoryPlayer = BaublesModule.getBaublesWrappedInventory(entityPlayer); } diff --git a/src/main/java/gregtech/api/items/metaitem/MetaItem.java b/src/main/java/gregtech/api/items/metaitem/MetaItem.java index ba1f713f010..f2674add426 100644 --- a/src/main/java/gregtech/api/items/metaitem/MetaItem.java +++ b/src/main/java/gregtech/api/items/metaitem/MetaItem.java @@ -1,7 +1,6 @@ package gregtech.api.items.metaitem; import gregtech.api.GTValues; -import gregtech.api.GregTechAPI; import gregtech.api.capability.GregtechCapabilities; import gregtech.api.capability.IElectricItem; import gregtech.api.capability.IFilteredFluidContainer; @@ -12,7 +11,18 @@ import gregtech.api.items.OreDictNames; import gregtech.api.items.gui.ItemUIFactory; import gregtech.api.items.gui.PlayerInventoryHolder; -import gregtech.api.items.metaitem.stats.*; +import gregtech.api.items.metaitem.stats.IEnchantabilityHelper; +import gregtech.api.items.metaitem.stats.IFoodBehavior; +import gregtech.api.items.metaitem.stats.IItemBehaviour; +import gregtech.api.items.metaitem.stats.IItemCapabilityProvider; +import gregtech.api.items.metaitem.stats.IItemColorProvider; +import gregtech.api.items.metaitem.stats.IItemComponent; +import gregtech.api.items.metaitem.stats.IItemContainerItemProvider; +import gregtech.api.items.metaitem.stats.IItemDurabilityManager; +import gregtech.api.items.metaitem.stats.IItemMaxStackSizeProvider; +import gregtech.api.items.metaitem.stats.IItemNameProvider; +import gregtech.api.items.metaitem.stats.IItemUseManager; +import gregtech.api.items.metaitem.stats.ISubItemHandler; import gregtech.api.recipes.ingredients.IntCircuitIngredient; import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.material.Material; @@ -20,8 +30,10 @@ import gregtech.api.unification.stack.ItemMaterialInfo; import gregtech.api.util.GTUtility; import gregtech.api.util.LocalizationUtils; +import gregtech.api.util.Mods; import gregtech.client.utils.ToolChargeBarRenderer; import gregtech.common.ConfigHolder; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.ModelBakery; @@ -40,7 +52,12 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.*; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.client.model.ModelLoader; @@ -72,23 +89,31 @@ import java.time.Duration; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; /** - * MetaItem is item that can have up to Short.MAX_VALUE items inside one id. - * These items even can be edible, have custom behaviours, be electric or act like fluid containers! - * They can also have different burn time, plus be handheld, oredicted or invisible! - * They also can be reactor components. + * MetaItem is item that can have up to Short.MAX_VALUE items inside one id. These items even can be edible, have custom + * behaviours, be electric or act like fluid containers! They can also have different burn time, plus be handheld, + * oredicted or invisible! They also can be reactor components. *

* You can also extend this class and occupy some of it's MetaData, and just pass an meta offset in constructor, and * everything will work properly. *

* Items are added in MetaItem via {@link #addItem(int, String)}. You will get {@link MetaValueItem} instance, which you * can configure in builder-alike pattern: - * {@code addItem(0, "test_item").addStats(new ElectricStats(10000, 1, false)) } - * This will add single-use (not rechargeable) LV battery with initial capacity 10000 EU + * {@code addItem(0, "test_item").addStats(new ElectricStats(10000, 1, false)) } This will add single-use (not + * rechargeable) LV battery with initial capacity 10000 EU */ -@Optional.Interface(modid = GTValues.MODID_ECORE, iface = "com.enderio.core.common.interfaces.IOverlayRenderAware") +@Optional.Interface( + modid = Mods.Names.ENDER_CORE, + iface = "com.enderio.core.common.interfaces.IOverlayRenderAware") public abstract class MetaItem.MetaValueItem> extends Item implements ItemUIFactory, IOverlayRenderAware { @@ -107,11 +132,12 @@ public static List> getMetaItems() { protected final short metaItemOffset; - private CreativeTabs[] defaultCreativeTabs = new CreativeTabs[] { GregTechAPI.TAB_GREGTECH }; + private CreativeTabs[] defaultCreativeTabs = new CreativeTabs[] { GTCreativeTabs.TAB_GREGTECH }; private final Set additionalCreativeTabs = new ObjectArraySet<>(); + private String translationKey = "metaitem"; + public MetaItem(short metaItemOffset) { - setTranslationKey("meta_item"); setHasSubtypes(true); this.metaItemOffset = metaItemOffset; META_ITEMS.add(this); @@ -354,6 +380,7 @@ public void onPlayerStoppedUsing(@NotNull ItemStack stack, @NotNull World world, } } + @NotNull @Override public ItemStack onItemUseFinish(@NotNull ItemStack stack, @NotNull World world, @NotNull EntityLivingBase player) { if (player instanceof EntityPlayer) { @@ -527,10 +554,28 @@ public boolean shouldCauseReequipAnimation(@NotNull ItemStack oldStack, @NotNull return !ItemStack.areItemStacksEqual(oldStack, newStack); } + @NotNull @Override - public String getTranslationKey(ItemStack stack) { - T metaItem = getItem(stack); - return metaItem == null ? getTranslationKey() : getTranslationKey() + "." + metaItem.unlocalizedName; + public MetaItem setTranslationKey(@NotNull String key) { + this.translationKey = Objects.requireNonNull(key, "key == null"); + return this; + } + + @NotNull + @Override + public String getTranslationKey() { + return getTranslationKey((T) null); + } + + @NotNull + @Override + public String getTranslationKey(@NotNull ItemStack stack) { + return getTranslationKey(getItem(stack)); + } + + @NotNull + protected String getTranslationKey(@Nullable T metaValueItem) { + return metaValueItem == null ? this.translationKey : this.translationKey + "." + metaValueItem.unlocalizedName; } @NotNull @@ -541,7 +586,7 @@ public String getItemStackDisplayName(ItemStack stack) { if (item == null) { return "invalid item"; } - String unlocalizedName = String.format("metaitem.%s.name", item.unlocalizedName); + String unlocalizedName = getTranslationKey(item) + ".name"; if (item.getNameProvider() != null) { return item.getNameProvider().getItemStackDisplayName(stack, unlocalizedName); } @@ -564,7 +609,7 @@ public void addInformation(@NotNull ItemStack itemStack, @Nullable World worldIn @NotNull ITooltipFlag tooltipFlag) { T item = getItem(itemStack); if (item == null) return; - String unlocalizedTooltip = "metaitem." + item.unlocalizedName + ".tooltip"; + String unlocalizedTooltip = getTranslationKey(item) + ".tooltip"; if (I18n.hasKey(unlocalizedTooltip)) { Collections.addAll(lines, LocalizationUtils.formatLines(unlocalizedTooltip)); } @@ -660,25 +705,27 @@ public ItemStack getContainerItem(@NotNull ItemStack itemStack) { @NotNull @Override - public CreativeTabs[] getCreativeTabs() { + public CreativeTabs @NotNull [] getCreativeTabs() { if (additionalCreativeTabs.isEmpty()) return defaultCreativeTabs; // short circuit Set tabs = new ObjectArraySet<>(additionalCreativeTabs); tabs.addAll(Arrays.asList(defaultCreativeTabs)); return tabs.toArray(new CreativeTabs[0]); } + @NotNull @Override - public MetaItem setCreativeTab(CreativeTabs tab) { + public MetaItem setCreativeTab(@NotNull CreativeTabs tab) { this.defaultCreativeTabs = new CreativeTabs[] { tab }; return this; } - public MetaItem setCreativeTabs(CreativeTabs... tabs) { + @NotNull + public MetaItem setCreativeTabs(@NotNull CreativeTabs @NotNull... tabs) { this.defaultCreativeTabs = tabs; return this; } - public void addAdditionalCreativeTabs(CreativeTabs... tabs) { + public void addAdditionalCreativeTabs(@NotNull CreativeTabs @NotNull... tabs) { for (CreativeTabs tab : tabs) { if (!ArrayUtils.contains(defaultCreativeTabs, tab) && tab != CreativeTabs.SEARCH) { additionalCreativeTabs.add(tab); @@ -687,7 +734,7 @@ public void addAdditionalCreativeTabs(CreativeTabs... tabs) { } @Override - protected boolean isInCreativeTab(CreativeTabs tab) { + protected boolean isInCreativeTab(@NotNull CreativeTabs tab) { return tab == CreativeTabs.SEARCH || ArrayUtils.contains(defaultCreativeTabs, tab) || additionalCreativeTabs.contains(tab); diff --git a/src/main/java/gregtech/api/items/toolitem/IGTTool.java b/src/main/java/gregtech/api/items/toolitem/IGTTool.java index 300c36f537e..94ae45effb9 100644 --- a/src/main/java/gregtech/api/items/toolitem/IGTTool.java +++ b/src/main/java/gregtech/api/items/toolitem/IGTTool.java @@ -24,6 +24,7 @@ import gregtech.api.unification.ore.OrePrefix; import gregtech.api.unification.stack.UnificationEntry; import gregtech.api.util.GTUtility; +import gregtech.api.util.Mods; import gregtech.api.util.TextFormattingUtil; import gregtech.client.utils.ToolChargeBarRenderer; import gregtech.client.utils.TooltipHelper; @@ -40,6 +41,7 @@ import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.attributes.AttributeModifier; +import net.minecraft.entity.item.EntityMinecart; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.inventory.EntityEquipmentSlot; @@ -68,6 +70,8 @@ import forestry.api.arboriculture.IToolGrafter; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import mods.railcraft.api.items.IToolCrowbar; +import mrtjp.projectred.api.IScrewdriver; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -86,15 +90,24 @@ * Backing of every variation of a GT Tool */ @Optional.InterfaceList({ - @Optional.Interface(modid = GTValues.MODID_APPENG, iface = "appeng.api.implementations.items.IAEWrench"), - @Optional.Interface(modid = GTValues.MODID_BC, iface = "buildcraft.api.tools.IToolWrench"), - @Optional.Interface(modid = GTValues.MODID_COFH, iface = "cofh.api.item.IToolHammer"), - @Optional.Interface(modid = GTValues.MODID_EIO, iface = "crazypants.enderio.api.tool.ITool"), - @Optional.Interface(modid = GTValues.MODID_FR, iface = "forestry.api.arboriculture.IToolGrafter"), - @Optional.Interface(modid = GTValues.MODID_ECORE, + @Optional.Interface(modid = Mods.Names.APPLIED_ENERGISTICS2, + iface = "appeng.api.implementations.items.IAEWrench"), + @Optional.Interface(modid = Mods.Names.BUILD_CRAFT_CORE, + iface = "buildcraft.api.tools.IToolWrench"), + @Optional.Interface(modid = Mods.Names.COFH_CORE, + iface = "cofh.api.item.IToolHammer"), + @Optional.Interface(modid = Mods.Names.ENDER_IO, + iface = "crazypants.enderio.api.tool.ITool"), + @Optional.Interface(modid = Mods.Names.FORESTRY, + iface = "forestry.api.arboriculture.IToolGrafter"), + @Optional.Interface(modid = Mods.Names.PROJECT_RED_CORE, + iface = "mrtjp.projectred.api.IScrewdriver"), + @Optional.Interface(modid = Mods.Names.RAILCRAFT, + iface = "mods.railcraft.api.items.IToolCrowbar"), + @Optional.Interface(modid = Mods.Names.ENDER_CORE, iface = "com.enderio.core.common.interfaces.IOverlayRenderAware") }) public interface IGTTool extends ItemUIFactory, IAEWrench, IToolWrench, IToolHammer, ITool, IToolGrafter, - IOverlayRenderAware { + IOverlayRenderAware, IScrewdriver, IToolCrowbar { /** * @return the modid of the tool @@ -1042,4 +1055,47 @@ default float getSaplingModifier(ItemStack stack, World world, EntityPlayer play default void renderItemOverlayIntoGUI(@NotNull ItemStack stack, int xPosition, int yPosition) { ToolChargeBarRenderer.renderBarsTool(this, stack, xPosition, yPosition); } + + // IScrewdriver + @Override + default boolean canUse(EntityPlayer player, ItemStack stack) { + return get().getToolClasses(stack).contains(ToolClasses.SCREWDRIVER); + } + + @Override + default void damageScrewdriver(EntityPlayer player, ItemStack stack) { + damageItem(stack, player); + } + + // IToolCrowbar + + @Override + default boolean canWhack(EntityPlayer player, EnumHand hand, ItemStack crowbar, BlockPos pos) { + return get().getToolClasses(crowbar).contains(ToolClasses.CROWBAR); + } + + @Override + default void onWhack(EntityPlayer player, EnumHand hand, ItemStack crowbar, BlockPos pos) { + damageItem(crowbar, player); + } + + @Override + default boolean canLink(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart) { + return get().getToolClasses(crowbar).contains(ToolClasses.CROWBAR); + } + + @Override + default void onLink(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart) { + damageItem(crowbar, player); + } + + @Override + default boolean canBoost(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart) { + return get().getToolClasses(crowbar).contains(ToolClasses.CROWBAR); + } + + @Override + default void onBoost(EntityPlayer player, EnumHand hand, ItemStack crowbar, EntityMinecart cart) { + damageItem(crowbar, player); + } } diff --git a/src/main/java/gregtech/api/items/toolitem/ItemGTAxe.java b/src/main/java/gregtech/api/items/toolitem/ItemGTAxe.java index 5acd7f41780..2537884399a 100644 --- a/src/main/java/gregtech/api/items/toolitem/ItemGTAxe.java +++ b/src/main/java/gregtech/api/items/toolitem/ItemGTAxe.java @@ -1,7 +1,7 @@ package gregtech.api.items.toolitem; -import gregtech.api.GregTechAPI; import gregtech.api.util.LocalizationUtils; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.state.IBlockState; import net.minecraft.client.util.ITooltipFlag; @@ -14,7 +14,12 @@ import net.minecraft.item.ItemAxe; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.*; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; @@ -59,7 +64,7 @@ protected ItemGTAxe(String domain, String id, int tier, IGTToolDefinition toolSt this.secondaryOreDicts = secondaryOreDicts; this.markerItem = markerItem; setMaxStackSize(1); - setCreativeTab(GregTechAPI.TAB_GREGTECH_TOOLS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_TOOLS); setTranslationKey("gt.tool." + id + ".name"); setRegistryName(domain, id); } diff --git a/src/main/java/gregtech/api/items/toolitem/ItemGTHoe.java b/src/main/java/gregtech/api/items/toolitem/ItemGTHoe.java index 7f52587be7c..13e14e8e574 100644 --- a/src/main/java/gregtech/api/items/toolitem/ItemGTHoe.java +++ b/src/main/java/gregtech/api/items/toolitem/ItemGTHoe.java @@ -1,7 +1,7 @@ package gregtech.api.items.toolitem; -import gregtech.api.GregTechAPI; import gregtech.api.util.LocalizationUtils; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.state.IBlockState; import net.minecraft.client.util.ITooltipFlag; @@ -14,7 +14,12 @@ import net.minecraft.item.ItemHoe; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.*; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; @@ -59,7 +64,7 @@ protected ItemGTHoe(String domain, String id, int tier, IGTToolDefinition toolSt this.secondaryOreDicts = secondaryOreDicts; this.markerItem = markerItem; setMaxStackSize(1); - setCreativeTab(GregTechAPI.TAB_GREGTECH_TOOLS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_TOOLS); setTranslationKey("gt.tool." + id + ".name"); setRegistryName(domain, id); } diff --git a/src/main/java/gregtech/api/items/toolitem/ItemGTSword.java b/src/main/java/gregtech/api/items/toolitem/ItemGTSword.java index 5b95f6f3718..2786f91d558 100644 --- a/src/main/java/gregtech/api/items/toolitem/ItemGTSword.java +++ b/src/main/java/gregtech/api/items/toolitem/ItemGTSword.java @@ -1,7 +1,7 @@ package gregtech.api.items.toolitem; -import gregtech.api.GregTechAPI; import gregtech.api.util.LocalizationUtils; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.state.IBlockState; import net.minecraft.client.util.ITooltipFlag; @@ -14,7 +14,12 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.ItemSword; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.*; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; @@ -61,7 +66,7 @@ protected ItemGTSword(String domain, String id, int tier, IGTToolDefinition tool this.secondaryOreDicts = secondaryOreDicts; this.markerItem = markerItem; setMaxStackSize(1); - setCreativeTab(GregTechAPI.TAB_GREGTECH_TOOLS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_TOOLS); setTranslationKey("gt.tool." + id + ".name"); setRegistryName(domain, id); } diff --git a/src/main/java/gregtech/api/items/toolitem/ItemGTTool.java b/src/main/java/gregtech/api/items/toolitem/ItemGTTool.java index edcfa83126d..375aadb845c 100644 --- a/src/main/java/gregtech/api/items/toolitem/ItemGTTool.java +++ b/src/main/java/gregtech/api/items/toolitem/ItemGTTool.java @@ -1,7 +1,7 @@ package gregtech.api.items.toolitem; -import gregtech.api.GregTechAPI; import gregtech.api.util.LocalizationUtils; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.state.IBlockState; import net.minecraft.client.util.ITooltipFlag; @@ -14,7 +14,12 @@ import net.minecraft.item.ItemStack; import net.minecraft.item.ItemTool; import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.util.*; +import net.minecraft.util.ActionResult; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; @@ -65,7 +70,7 @@ protected ItemGTTool(String domain, String id, int tier, IGTToolDefinition toolS this.secondaryOreDicts = secondaryOreDicts; this.markerItem = markerItem; setMaxStackSize(1); - setCreativeTab(GregTechAPI.TAB_GREGTECH_TOOLS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_TOOLS); setTranslationKey("gt.tool." + id + ".name"); setRegistryName(domain, id); } diff --git a/src/main/java/gregtech/api/items/toolitem/ToolHelper.java b/src/main/java/gregtech/api/items/toolitem/ToolHelper.java index 2e0a3335df0..23d3145ca9b 100644 --- a/src/main/java/gregtech/api/items/toolitem/ToolHelper.java +++ b/src/main/java/gregtech/api/items/toolitem/ToolHelper.java @@ -270,7 +270,7 @@ public static void damageItem(@NotNull ItemStack stack, @Nullable EntityLivingBa if (electricItem != null) { electricItem.discharge(electricDamage, tool.getElectricTier(), true, false, false); if (electricItem.getCharge() > 0 && - random.nextInt(100) > ConfigHolder.tools.rngDamageElectricTools) { + random.nextInt(100) >= ConfigHolder.tools.rngDamageElectricTools) { return; } } else { diff --git a/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java b/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java index aa223d66de7..a1ca10b2af0 100644 --- a/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java +++ b/src/main/java/gregtech/api/metatileentity/MetaTileEntity.java @@ -6,9 +6,18 @@ import gregtech.api.capability.GregtechDataCodes; import gregtech.api.capability.GregtechTileCapabilities; import gregtech.api.capability.IControllable; +import gregtech.api.capability.IDataStickIntractable; import gregtech.api.capability.IEnergyContainer; -import gregtech.api.capability.impl.*; -import gregtech.api.cover.*; +import gregtech.api.capability.impl.AbstractRecipeLogic; +import gregtech.api.capability.impl.FluidHandlerProxy; +import gregtech.api.capability.impl.FluidTankList; +import gregtech.api.capability.impl.ItemHandlerProxy; +import gregtech.api.capability.impl.NotifiableFluidTank; +import gregtech.api.cover.Cover; +import gregtech.api.cover.CoverHolder; +import gregtech.api.cover.CoverRayTracer; +import gregtech.api.cover.CoverSaveHandler; +import gregtech.api.cover.CoverUtil; import gregtech.api.gui.ModularUI; import gregtech.api.items.itemhandlers.GTItemStackHandler; import gregtech.api.items.toolitem.ToolClasses; @@ -16,15 +25,18 @@ import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; import gregtech.api.metatileentity.interfaces.ISyncedTileEntity; import gregtech.api.mui.GTGuiTheme; -import gregtech.api.mui.GTGuis; import gregtech.api.mui.GregTechGuiScreen; +import gregtech.api.mui.factory.MetaTileEntityGuiFactory; import gregtech.api.recipes.RecipeMap; import gregtech.api.util.GTLog; import gregtech.api.util.GTTransferUtils; import gregtech.api.util.GTUtility; +import gregtech.api.util.Mods; import gregtech.client.renderer.texture.Textures; import gregtech.client.utils.BloomEffectUtil; import gregtech.common.ConfigHolder; +import gregtech.common.creativetab.GTCreativeTabs; +import gregtech.common.items.MetaItems; import net.minecraft.block.Block; import net.minecraft.block.state.BlockFaceShape; @@ -40,7 +52,13 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.PacketBuffer; import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.*; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.util.text.TextComponentTranslation; import net.minecraft.world.World; @@ -51,6 +69,7 @@ import net.minecraftforge.fluids.FluidUtil; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.Optional.Method; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -70,7 +89,7 @@ import codechicken.lib.vec.Cuboid6; import codechicken.lib.vec.Matrix4; import com.cleanroommc.modularui.api.IGuiHolder; -import com.cleanroommc.modularui.manager.GuiCreationContext; +import com.cleanroommc.modularui.factory.PosGuiData; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.screen.ModularScreen; import com.cleanroommc.modularui.value.sync.GuiSyncManager; @@ -84,13 +103,17 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; import static gregtech.api.capability.GregtechDataCodes.*; -public abstract class MetaTileEntity implements ISyncedTileEntity, CoverHolder, IVoidable, IGuiHolder { +public abstract class MetaTileEntity implements ISyncedTileEntity, CoverHolder, IVoidable, IGuiHolder { public static final IndexedCuboid6 FULL_CUBE_COLLISION = new IndexedCuboid6(null, Cuboid6.full); @@ -132,6 +155,7 @@ public abstract class MetaTileEntity implements ISyncedTileEntity, CoverHolder, protected boolean muffled = false; private int playSoundCooldown = 0; + private int lastTick = 0; public MetaTileEntity(ResourceLocation metaTileEntityId) { this.metaTileEntityId = metaTileEntityId; @@ -328,7 +352,7 @@ public void getSubItems(CreativeTabs creativeTab, NonNullList subItem * MachineItemBlock#addCreativeTab(CreativeTabs) */ public boolean isInCreativeTab(CreativeTabs creativeTab) { - return creativeTab == CreativeTabs.SEARCH || creativeTab == GregTechAPI.TAB_GREGTECH_MACHINES; + return creativeTab == CreativeTabs.SEARCH || creativeTab == GTCreativeTabs.TAB_GREGTECH_MACHINES; } public String getItemSubTypeId(ItemStack itemStack) { @@ -437,7 +461,7 @@ public boolean usesMui2() { @SideOnly(Side.CLIENT) @Override - public final ModularScreen createScreen(GuiCreationContext creationContext, ModularPanel mainPanel) { + public final ModularScreen createScreen(PosGuiData posGuiData, ModularPanel mainPanel) { return new GregTechGuiScreen(mainPanel, getUITheme()); } @@ -446,8 +470,7 @@ public GTGuiTheme getUITheme() { } @Override - public ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManager guiSyncManager, - boolean isClient) { + public ModularPanel buildUI(PosGuiData guiData, GuiSyncManager guiSyncManager) { return null; } @@ -466,10 +489,16 @@ public final void onCoverLeftClick(EntityPlayer playerIn, CuboidRayTraceResult r public boolean onRightClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing, CuboidRayTraceResult hitResult) { ItemStack heldStack = playerIn.getHeldItem(hand); + if (this instanceof IDataStickIntractable dsi) { + if (MetaItems.TOOL_DATA_STICK.isItemEqual(heldStack) && dsi.onDataStickRightClick(playerIn, heldStack)) { + return true; + } + } + if (!playerIn.isSneaking() && openGUIOnRightClick()) { if (getWorld() != null && !getWorld().isRemote) { if (usesMui2()) { - GTGuis.MTE.open(playerIn, getWorld(), getPos()); + MetaTileEntityGuiFactory.open(playerIn, this); } else { MetaTileEntityUIFactory.INSTANCE.openUI(getHolder(), (EntityPlayerMP) playerIn); } @@ -621,7 +650,14 @@ public boolean onHardHammerClick(EntityPlayer playerIn, EnumHand hand, EnumFacin return true; } - public void onLeftClick(EntityPlayer player, EnumFacing facing, CuboidRayTraceResult hitResult) {} + public void onLeftClick(EntityPlayer player, EnumFacing facing, CuboidRayTraceResult hitResult) { + if (this instanceof IDataStickIntractable dsi) { + ItemStack stack = player.getHeldItemMainhand(); + if (MetaItems.TOOL_DATA_STICK.isItemEqual(stack)) { + dsi.onDataStickLeftClick(player, stack); + } + } + } /** * @return true if the player must sneak to rotate this metatileentity, otherwise false @@ -790,6 +826,13 @@ private void updateLightValue() { } public void update() { + if (!allowTickAcceleration()) { + int currentTick = FMLCommonHandler.instance().getMinecraftServerInstance().getTickCounter(); + if (currentTick == lastTick) { + return; + } + lastTick = currentTick; + } for (MTETrait mteTrait : this.mteTraits.values()) { if (shouldUpdate(mteTrait)) { mteTrait.update(); @@ -805,6 +848,15 @@ public void update() { } } + /** + * @return Whether this machine is allowed to be tick accelerated by external means. This does NOT + * apply to World Accelerators from GT, those will never work on machines. This refers to effects + * like Time in a Bottle, or Torcherino, or similar. + */ + public boolean allowTickAcceleration() { + return ConfigHolder.machines.allowTickAcceleration; + } + protected boolean shouldUpdate(MTETrait trait) { return true; } @@ -835,7 +887,8 @@ public final ItemStack getStackForm(int amount) { return new ItemStack(GregTechAPI.MACHINE, amount, metaTileEntityIntId); } - public final ItemStack getStackForm() { + @Override + public final @NotNull ItemStack getStackForm() { return getStackForm(1); } @@ -1528,17 +1581,17 @@ public boolean canVoidRecipeFluidOutputs() { } @NotNull - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public AECableType getCableConnectionType(@NotNull AEPartLocation part) { return AECableType.NONE; } @Nullable - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public AENetworkProxy getProxy() { return null; } - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public void gridChanged() {} } diff --git a/src/main/java/gregtech/api/metatileentity/MetaTileEntityHolder.java b/src/main/java/gregtech/api/metatileentity/MetaTileEntityHolder.java index 9d5662a1153..5c6487e6180 100644 --- a/src/main/java/gregtech/api/metatileentity/MetaTileEntityHolder.java +++ b/src/main/java/gregtech/api/metatileentity/MetaTileEntityHolder.java @@ -1,6 +1,5 @@ package gregtech.api.metatileentity; -import gregtech.api.GTValues; import gregtech.api.GregTechAPI; import gregtech.api.block.machines.BlockMachine; import gregtech.api.capability.GregtechDataCodes; @@ -8,6 +7,7 @@ import gregtech.api.gui.IUIHolder; import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; import gregtech.api.util.GTLog; +import gregtech.api.util.Mods; import gregtech.api.util.TextFormattingUtil; import gregtech.client.particle.GTNameTagParticle; import gregtech.client.particle.GTParticleManager; @@ -29,7 +29,6 @@ import net.minecraft.world.World; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.Constants.NBT; -import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.Optional.Interface; import net.minecraftforge.fml.common.Optional.InterfaceList; import net.minecraftforge.fml.common.Optional.Method; @@ -54,10 +53,11 @@ @InterfaceList(value = { @Interface(iface = "appeng.api.networking.security.IActionHost", - modid = GTValues.MODID_APPENG, + modid = Mods.Names.APPLIED_ENERGISTICS2, striprefs = true), - @Interface(iface = "appeng.me.helpers.IGridProxyable", modid = GTValues.MODID_APPENG, striprefs = true), -}) + @Interface(iface = "appeng.me.helpers.IGridProxyable", + modid = Mods.Names.APPLIED_ENERGISTICS2, + striprefs = true) }) public class MetaTileEntityHolder extends TickableTileEntityBase implements IGregTechTileEntity, IUIHolder, IWorldNameable, IActionHost, IGridProxyable { @@ -138,7 +138,7 @@ public void readFromNBT(@NotNull NBTTagCompound compound) { } else { GTLog.logger.error("Failed to load MetaTileEntity with invalid ID " + metaTileEntityIdRaw); } - if (Loader.isModLoaded(GTValues.MODID_APPENG)) { + if (Mods.AppliedEnergistics2.isModLoaded()) { readFromNBT_AENetwork(compound); } } @@ -154,7 +154,7 @@ public NBTTagCompound writeToNBT(@NotNull NBTTagCompound compound) { NBTTagCompound metaTileEntityData = new NBTTagCompound(); metaTileEntity.writeToNBT(metaTileEntityData); compound.setTag("MetaTileEntity", metaTileEntityData); - if (Loader.isModLoaded(GTValues.MODID_APPENG)) { + if (Mods.AppliedEnergistics2.isModLoaded()) { writeToNBT_AENetwork(compound); } } @@ -167,7 +167,7 @@ public void invalidate() { metaTileEntity.invalidate(); } super.invalidate(); - if (Loader.isModLoaded(GTValues.MODID_APPENG)) { + if (Mods.AppliedEnergistics2.isModLoaded()) { invalidateAE(); } } @@ -375,7 +375,7 @@ public void onChunkUnload() { if (metaTileEntity != null) { metaTileEntity.onUnload(); } - if (Loader.isModLoaded(GTValues.MODID_APPENG)) { + if (Mods.AppliedEnergistics2.isModLoaded()) { onChunkUnloadAE(); } } @@ -505,7 +505,7 @@ public ITextComponent getDisplayName() { @Nullable @Override - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public IGridNode getGridNode(@NotNull AEPartLocation part) { // Forbid it connects the faces it shouldn't connect. if (this.getCableConnectionType(part) == AECableType.NONE) { @@ -517,44 +517,44 @@ public IGridNode getGridNode(@NotNull AEPartLocation part) { @NotNull @Override - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public AECableType getCableConnectionType(@NotNull AEPartLocation part) { return metaTileEntity == null ? AECableType.NONE : metaTileEntity.getCableConnectionType(part); } @Override - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public void securityBreak() {} @NotNull @Override - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public IGridNode getActionableNode() { AENetworkProxy proxy = getProxy(); return proxy == null ? null : proxy.getNode(); } @Override - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public AENetworkProxy getProxy() { return metaTileEntity == null ? null : metaTileEntity.getProxy(); } @Override - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public DimensionalCoord getLocation() { return new DimensionalCoord(this); } @Override - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public void gridChanged() { if (metaTileEntity != null) { metaTileEntity.gridChanged(); } } - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public void readFromNBT_AENetwork(NBTTagCompound data) { AENetworkProxy proxy = getProxy(); if (proxy != null) { @@ -562,7 +562,7 @@ public void readFromNBT_AENetwork(NBTTagCompound data) { } } - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) public void writeToNBT_AENetwork(NBTTagCompound data) { AENetworkProxy proxy = getProxy(); if (proxy != null) { @@ -570,7 +570,7 @@ public void writeToNBT_AENetwork(NBTTagCompound data) { } } - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) void onChunkUnloadAE() { AENetworkProxy proxy = getProxy(); if (proxy != null) { @@ -578,7 +578,7 @@ void onChunkUnloadAE() { } } - @Method(modid = GTValues.MODID_APPENG) + @Method(modid = Mods.Names.APPLIED_ENERGISTICS2) void invalidateAE() { AENetworkProxy proxy = getProxy(); if (proxy != null) { diff --git a/src/main/java/gregtech/api/metatileentity/NeighborCacheTileEntityBase.java b/src/main/java/gregtech/api/metatileentity/NeighborCacheTileEntityBase.java index 473b950d63c..97200ab8518 100644 --- a/src/main/java/gregtech/api/metatileentity/NeighborCacheTileEntityBase.java +++ b/src/main/java/gregtech/api/metatileentity/NeighborCacheTileEntityBase.java @@ -7,6 +7,7 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import org.jetbrains.annotations.MustBeInvokedByOverriders; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,24 +29,34 @@ protected void invalidateNeighbors() { } } + @MustBeInvokedByOverriders @Override public void setWorld(@NotNull World worldIn) { super.setWorld(worldIn); invalidateNeighbors(); } + @MustBeInvokedByOverriders @Override public void setPos(@NotNull BlockPos posIn) { super.setPos(posIn); invalidateNeighbors(); } + @MustBeInvokedByOverriders @Override public void invalidate() { super.invalidate(); invalidateNeighbors(); } + @MustBeInvokedByOverriders + @Override + public void onChunkUnload() { + super.onChunkUnload(); + invalidateNeighbors(); + } + @Override public @Nullable TileEntity getNeighbor(@NotNull EnumFacing facing) { if (world == null || pos == null) return null; diff --git a/src/main/java/gregtech/api/metatileentity/WorkableTieredMetaTileEntity.java b/src/main/java/gregtech/api/metatileentity/WorkableTieredMetaTileEntity.java index 69c42322da8..5e648be0f60 100644 --- a/src/main/java/gregtech/api/metatileentity/WorkableTieredMetaTileEntity.java +++ b/src/main/java/gregtech/api/metatileentity/WorkableTieredMetaTileEntity.java @@ -1,7 +1,12 @@ package gregtech.api.metatileentity; import gregtech.api.GTValues; -import gregtech.api.capability.impl.*; +import gregtech.api.capability.impl.AbstractRecipeLogic; +import gregtech.api.capability.impl.EnergyContainerHandler; +import gregtech.api.capability.impl.FluidTankList; +import gregtech.api.capability.impl.NotifiableFluidTank; +import gregtech.api.capability.impl.NotifiableItemStackHandler; +import gregtech.api.capability.impl.RecipeLogicEnergy; import gregtech.api.items.itemhandlers.GTItemStackHandler; import gregtech.api.metatileentity.multiblock.ICleanroomProvider; import gregtech.api.metatileentity.multiblock.ICleanroomReceiver; @@ -34,7 +39,7 @@ public abstract class WorkableTieredMetaTileEntity extends TieredMetaTileEntity implements IDataInfoProvider, ICleanroomReceiver { - protected final RecipeLogicEnergy workable; + protected final AbstractRecipeLogic workable; protected final RecipeMap recipeMap; protected final ICubeRenderer renderer; @@ -63,7 +68,7 @@ public WorkableTieredMetaTileEntity(ResourceLocation metaTileEntityId, RecipeMap reinitializeEnergyContainer(); } - protected RecipeLogicEnergy createWorkable(RecipeMap recipeMap) { + protected AbstractRecipeLogic createWorkable(RecipeMap recipeMap) { return new RecipeLogicEnergy(this, recipeMap, () -> energyContainer); } diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java index dc6cb0e5d28..520a36dcebb 100644 --- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java +++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java @@ -11,4 +11,7 @@ public interface IMultiblockPart { default boolean canPartShare() { return true; } + + /** Called when distinct mode is toggled on the controller that this part is attached to */ + default void onDistinctChange(boolean newValue) {} } diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java index 933ec7b1259..54b5bd45b5c 100644 --- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java +++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java @@ -343,9 +343,9 @@ public void checkStructurePattern() { abilityPart.registerAbilities(abilityInstancesList); } } - parts.forEach(part -> part.addToMultiBlock(this)); this.multiblockParts.addAll(parts); this.multiblockAbilities.putAll(abilities); + parts.forEach(part -> part.addToMultiBlock(this)); this.structureFormed = true; writeCustomData(STRUCTURE_FORMED, buf -> buf.writeBoolean(true)); formStructure(context); diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java index a94c78ccdff..55723ef456d 100644 --- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java +++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java @@ -275,7 +275,7 @@ public boolean isMufflerFaceFree() { } /** - * @deprecated Use {@link gregtech.client.particle.VanillaParticleEffects#MUFFLER_SMOKE} instead. + * @deprecated Override {@link #getMufflerParticle()} instead. */ @ApiStatus.ScheduledForRemoval(inVersion = "2.9") @Deprecated @@ -284,6 +284,11 @@ public void runMufflerEffect(float xPos, float yPos, float zPos, float xSpd, flo getWorld().spawnParticle(EnumParticleTypes.SMOKE_LARGE, xPos, yPos, zPos, xSpd, ySpd, zSpd); } + @SideOnly(Side.CLIENT) + public @NotNull EnumParticleTypes getMufflerParticle() { + return EnumParticleTypes.SMOKE_LARGE; + } + /** * Sets the recovery items of this multiblock * diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java index 4301adbacce..d90c9b548db 100644 --- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java +++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java @@ -246,6 +246,7 @@ public boolean isDistinct() { public void setDistinct(boolean isDistinct) { this.isDistinct = isDistinct; recipeMapWorkable.onDistinctChanged(); + getMultiblockParts().forEach(part -> part.onDistinctChange(isDistinct)); // mark buses as changed on distinct toggle if (this.isDistinct) { this.notifiedItemInputList.addAll(this.getAbilities(MultiblockAbility.IMPORT_ITEMS)); diff --git a/src/main/java/gregtech/api/mui/GTGuiTextures.java b/src/main/java/gregtech/api/mui/GTGuiTextures.java index 434c90e9ce3..3c99abaee1e 100644 --- a/src/main/java/gregtech/api/mui/GTGuiTextures.java +++ b/src/main/java/gregtech/api/mui/GTGuiTextures.java @@ -4,6 +4,7 @@ import com.cleanroommc.modularui.drawable.UITexture; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; /** * GT MUI textures.
@@ -18,6 +19,7 @@ public class GTGuiTextures { public static class IDs { public static final String STANDARD_BACKGROUND = "gregtech_standard_bg"; + public static final String COVER_BACKGROUND = "gregtech_cover_bg"; public static final String BRONZE_BACKGROUND = "gregtech_bronze_bg"; public static final String STEEL_BACKGROUND = "gregtech_steel_bg"; public static final String PRIMITIVE_BACKGROUND = "gregtech_primitive_bg"; @@ -33,9 +35,9 @@ public static class IDs { } // ICONS - /** @apiNote You may want {@link GTGuiTextures#getLogo()} instead. */ + /** @apiNote You may want {@link GTGuiTextures#getLogo} instead. */ public static final UITexture GREGTECH_LOGO = fullImage("textures/gui/icon/gregtech_logo.png"); - /** @apiNote You may want {@link GTGuiTextures#getLogo()} instead. */ + /** @apiNote You may want {@link GTGuiTextures#getLogo} instead. */ public static final UITexture GREGTECH_LOGO_XMAS = fullImage("textures/gui/icon/gregtech_logo_xmas.png"); public static final UITexture GREGTECH_LOGO_DARK = fullImage("textures/gui/icon/gregtech_logo_dark.png"); // todo blinking GT logos @@ -52,7 +54,16 @@ public static class IDs { .location(GTValues.MODID, "textures/gui/base/background.png") .imageSize(176, 166) .adaptable(3) - .registerAsBackground(IDs.STANDARD_BACKGROUND, true) + .name(IDs.STANDARD_BACKGROUND) + .canApplyTheme() + .build(); + + public static final UITexture BACKGROUND_POPUP = UITexture.builder() + .location(GTValues.MODID, "textures/gui/base/background_popup.png") + .imageSize(195, 136) + .adaptable(4) + .name(IDs.COVER_BACKGROUND) + .canApplyTheme() .build(); // todo BORDERED/BOXED backgrounds will not be ported, if possible @@ -61,14 +72,14 @@ public static class IDs { .location(GTValues.MODID, "textures/gui/base/background_bronze.png") .imageSize(176, 166) .adaptable(3) - .registerAsBackground(IDs.BRONZE_BACKGROUND) + .name(IDs.BRONZE_BACKGROUND) .build(); public static final UITexture BACKGROUND_STEEL = UITexture.builder() .location(GTValues.MODID, "textures/gui/base/background_steel.png") .imageSize(176, 166) .adaptable(3) - .registerAsBackground(IDs.STEEL_BACKGROUND) + .name(IDs.STEEL_BACKGROUND) .build(); // todo move to textures/gui/base @@ -76,7 +87,7 @@ public static class IDs { .location(GTValues.MODID, "textures/gui/primitive/primitive_background.png") .imageSize(176, 166) .adaptable(3) - .registerAsBackground(IDs.PRIMITIVE_BACKGROUND) + .name(IDs.PRIMITIVE_BACKGROUND) .build(); // todo clipboard backgrounds, may deserve some redoing @@ -108,21 +119,22 @@ public static class IDs { .location(GTValues.MODID, "textures/gui/base/slot.png") .imageSize(18, 18) .adaptable(1) - .registerAsBackground(IDs.STANDARD_SLOT, true) + .name(IDs.STANDARD_SLOT) + .canApplyTheme() .build(); public static final UITexture SLOT_BRONZE = new UITexture.Builder() .location(GTValues.MODID, "textures/gui/base/slot_bronze.png") .imageSize(18, 18) .adaptable(1) - .registerAsBackground(IDs.BRONZE_SLOT) + .name(IDs.BRONZE_SLOT) .build(); public static final UITexture SLOT_STEEL = new UITexture.Builder() .location(GTValues.MODID, "textures/gui/base/slot_steel.png") .imageSize(18, 18) .adaptable(1) - .registerAsBackground(IDs.STEEL_SLOT) + .name(IDs.STEEL_SLOT) .build(); // todo move to textures/gui/base @@ -130,14 +142,15 @@ public static class IDs { .location(GTValues.MODID, "textures/gui/primitive/primitive_slot.png") .imageSize(18, 18) .adaptable(1) - .registerAsBackground(IDs.PRIMITIVE_SLOT) + .name(IDs.PRIMITIVE_SLOT) .build(); public static final UITexture FLUID_SLOT = new UITexture.Builder() .location(GTValues.MODID, "textures/gui/base/fluid_slot.png") .imageSize(18, 18) .adaptable(1) - .registerAsBackground(IDs.STANDARD_FLUID_SLOT, true) + .name(IDs.STANDARD_FLUID_SLOT) + .canApplyTheme() .build(); // todo bronze/steel/primitive fluid slots? @@ -253,10 +266,23 @@ public static class IDs { .location(GTValues.MODID, "textures/gui/widget/button.png") .imageSize(18, 18) .adaptable(1) - .registerAsIcon(IDs.STANDARD_BUTTON) + .name(IDs.STANDARD_BUTTON) .canApplyTheme() .build(); + public static final UITexture MC_BUTTON = new UITexture.Builder() + .location("modularui", "gui/widgets/mc_button.png") // todo + .imageSize(16, 32) + .uv(0.0f, 0.0f, 1.0f, 0.5f) + .adaptable(1) + .build(); + + public static final UITexture MC_BUTTON_DISABLED = new UITexture.Builder() + .location("modularui", "gui/widgets/mc_button_disabled.png") // todo + .imageSize(16, 16) + .adaptable(1) + .build(); + // BUTTON OVERLAYS public static final UITexture BUTTON_ITEM_OUTPUT = fullImage("textures/gui/widget/button_item_output_overlay.png"); @@ -266,6 +292,10 @@ public static class IDs { "textures/gui/widget/button_auto_collapse_overlay.png"); public static final UITexture BUTTON_X = fullImage("textures/gui/widget/button_x_overlay.png", true); + public static final UITexture BUTTON_CROSS = fullImage("textures/gui/widget/button_cross.png"); + public static final UITexture BUTTON_REDSTONE_ON = fullImage("textures/gui/widget/button_redstone_on.png"); + public static final UITexture BUTTON_REDSTONE_OFF = fullImage("textures/gui/widget/button_redstone_off.png"); + // PROGRESS BARS public static final UITexture PROGRESS_BAR_ARC_FURNACE = progressBar( "textures/gui/progress_bar/progress_bar_arc_furnace.png", true); @@ -444,7 +474,11 @@ private static UITexture progressBar(String path, int width, int height, boolean } // todo steam logos? multi indicator blinking logos? - public static UITexture getLogo() { + public static @NotNull UITexture getLogo(GTGuiTheme theme) { + if (theme != null) { + UITexture logo = theme.getLogo(); + if (logo != null) return logo; + } return GTValues.XMAS.get() ? GREGTECH_LOGO_XMAS : GREGTECH_LOGO; } } diff --git a/src/main/java/gregtech/api/mui/GTGuiTheme.java b/src/main/java/gregtech/api/mui/GTGuiTheme.java index c23754eea94..fce37223e96 100644 --- a/src/main/java/gregtech/api/mui/GTGuiTheme.java +++ b/src/main/java/gregtech/api/mui/GTGuiTheme.java @@ -1,43 +1,60 @@ package gregtech.api.mui; +import gregtech.api.cover.CoverWithUI; import gregtech.common.ConfigHolder; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import com.cleanroommc.modularui.api.ITheme; import com.cleanroommc.modularui.api.IThemeApi; +import com.cleanroommc.modularui.drawable.UITexture; +import com.cleanroommc.modularui.screen.Tooltip; import com.cleanroommc.modularui.theme.ReloadThemeEvent; import com.cleanroommc.modularui.utils.JsonBuilder; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import java.util.function.Supplier; public class GTGuiTheme { private static final List THEMES = new ArrayList<>(); - public static final GTGuiTheme STANDARD = new Builder("gregtech_standard") + public static final GTGuiTheme STANDARD = templateBuilder("gregtech_standard") .panel(GTGuiTextures.IDs.STANDARD_BACKGROUND) .itemSlot(GTGuiTextures.IDs.STANDARD_SLOT) .fluidSlot(GTGuiTextures.IDs.STANDARD_FLUID_SLOT) .color(ConfigHolder.client.defaultUIColor) - .toggleButton(GTGuiTextures.IDs.STANDARD_BUTTON, + .button(GTGuiTextures.IDs.STANDARD_BUTTON) + .simpleToggleButton(GTGuiTextures.IDs.STANDARD_BUTTON, GTGuiTextures.IDs.STANDARD_SLOT, ConfigHolder.client.defaultUIColor) .build(); - public static final GTGuiTheme BRONZE = new Builder("gregtech_bronze") + public static final GTGuiTheme COVER = templateBuilder("gregtech_cover") + .panel(GTGuiTextures.IDs.COVER_BACKGROUND) + .itemSlot(GTGuiTextures.IDs.STANDARD_SLOT) + .fluidSlot(GTGuiTextures.IDs.STANDARD_FLUID_SLOT) + .color(ConfigHolder.client.defaultUIColor) + .textColor(CoverWithUI.UI_TEXT_COLOR) + .build(); + + // TODO Multiblock theme for display texture, logo changes + + public static final GTGuiTheme BRONZE = templateBuilder("gregtech_bronze") .panel(GTGuiTextures.IDs.BRONZE_BACKGROUND) .itemSlot(GTGuiTextures.IDs.BRONZE_SLOT) .build(); - public static final GTGuiTheme STEEL = new Builder("gregtech_steel") + public static final GTGuiTheme STEEL = templateBuilder("gregtech_steel") .panel(GTGuiTextures.IDs.STEEL_BACKGROUND) .itemSlot(GTGuiTextures.IDs.STEEL_SLOT) .build(); - public static final GTGuiTheme PRIMITIVE = new Builder("gregtech_primitive") + public static final GTGuiTheme PRIMITIVE = templateBuilder("gregtech_primitive") .panel(GTGuiTextures.IDs.PRIMITIVE_BACKGROUND) .itemSlot(GTGuiTextures.IDs.PRIMITIVE_SLOT) .build(); @@ -47,6 +64,8 @@ public class GTGuiTheme { private final List> elementBuilder; private final JsonBuilder jsonBuilder; + private Supplier logo; + private GTGuiTheme(String themeId) { this.themeId = themeId; this.jsonBuilder = new JsonBuilder(); @@ -58,6 +77,15 @@ public String getId() { return themeId; } + public ITheme getMuiTheme() { + return IThemeApi.get().getTheme(themeId); + } + + public @Nullable UITexture getLogo() { + if (logo == null) return null; + return logo.get(); + } + private void register() { buildJson(); IThemeApi.get().registerTheme(themeId, jsonBuilder); @@ -77,6 +105,14 @@ public static void onReloadThemes(ReloadThemeEvent.Pre event) { THEMES.forEach(GTGuiTheme::buildJson); } + public static Builder templateBuilder(String themeId) { + Builder builder = new Builder(themeId); + builder.openCloseAnimation(0); + builder.tooltipPos(Tooltip.Pos.NEXT_TO_MOUSE); + builder.smoothProgressBar(true); + return builder; + } + public static class Builder { private final GTGuiTheme theme; @@ -110,6 +146,28 @@ public Builder globalHoverBackground(String hoverBackgroundId) { return this; } + /** + * Set the window open/close animation speed. Overrides global cfg. + * + * @param rate the rate in frames to play the open/close animation over, or 0 for no animation + */ + public Builder openCloseAnimation(int rate) { + theme.elementBuilder.add(b -> b.add("openCloseAnimation", rate)); + return this; + } + + /** Set whether progress bars should animate smoothly. Overrides global cfg. */ + public Builder smoothProgressBar(boolean smoothBar) { + theme.elementBuilder.add(b -> b.add("smoothProgressBar", smoothBar)); + return this; + } + + /** Set the tooltip pos for this theme. Overrides global cfg. */ + public Builder tooltipPos(Tooltip.Pos tooltipPos) { + theme.elementBuilder.add(b -> b.add("tooltipPos", tooltipPos.name())); + return this; + } + /** Set a global UI coloration for this theme. */ public Builder color(int color) { theme.elementBuilder.add(b -> b.add("color", color)); @@ -130,8 +188,6 @@ public Builder textShadow() { /** * Set a custom panel (background texture) for UIs with this theme. - * This ID must correspond with a {@link com.cleanroommc.modularui.drawable.UITexture} that is - * registered using {@link com.cleanroommc.modularui.drawable.GuiTextures#registerBackground}! */ public Builder panel(String panelId) { theme.elementBuilder.add(b -> b @@ -144,28 +200,36 @@ public Builder panel(String panelId) { /** * Set a custom button texture for UIs with this theme. - * This ID must correspond with a {@link com.cleanroommc.modularui.drawable.UITexture} that is - * registered using {@link com.cleanroommc.modularui.drawable.GuiTextures#registerIcon}! */ public Builder button(String buttonId) { - return button(buttonId, 0xFFFFFFFF, false); + return button(buttonId, buttonId, 0xFFFFFFFF, false); + } + + /** + * Set a custom button texture for UIs with this theme. + * + * @param buttonId The ID of the button texture + * @param hoverId The ID of the button texture while hovering over the button with your mouse + */ + public Builder button(String buttonId, String hoverId) { + return button(buttonId, hoverId, 0xFFFFFFFF, false); } /** * Set a custom button texture for UIs with this theme. - * This ID must correspond with a {@link com.cleanroommc.modularui.drawable.UITexture} that is - * registered using {@link com.cleanroommc.modularui.drawable.GuiTextures#registerIcon}! * * @param buttonId The ID of the button texture + * @param hoverId The ID of the button texture while hovering over the button with your mouse * @param textColor The color of text overlaid on this button * @param textShadow If text overlaid on this button should have a text shadow */ - public Builder button(String buttonId, int textColor, boolean textShadow) { + public Builder button(String buttonId, String hoverId, int textColor, boolean textShadow) { theme.elementBuilder.add(b -> b .add("button", new JsonBuilder() .add("background", new JsonBuilder() .add("type", "texture") .add("id", buttonId)) + .add("hoverBackground", hoverId) .add("textColor", textColor) .add("textShadow", textShadow))); return this; @@ -173,8 +237,6 @@ public Builder button(String buttonId, int textColor, boolean textShadow) { /** * Set a custom item slot texture for UIs with this theme. - * This ID must correspond with a {@link com.cleanroommc.modularui.drawable.UITexture} that is - * registered using {@link com.cleanroommc.modularui.drawable.GuiTextures#registerIcon}! */ public Builder itemSlot(String itemSlotId) { return itemSlot(itemSlotId, 0x60FFFFFF); @@ -182,8 +244,6 @@ public Builder itemSlot(String itemSlotId) { /** * Set a custom item slot texture for UIs with this theme. - * This ID must correspond with a {@link com.cleanroommc.modularui.drawable.UITexture} that is - * registered using {@link com.cleanroommc.modularui.drawable.GuiTextures#registerIcon}! * * @param itemSlotId The ID of the item slot texture * @param hoverColor The color of the tooltip hover box for this widget @@ -200,8 +260,6 @@ public Builder itemSlot(String itemSlotId, int hoverColor) { /** * Set a custom fluid slot texture for UIs with this theme. - * This ID must correspond with a {@link com.cleanroommc.modularui.drawable.UITexture} that is - * registered using {@link com.cleanroommc.modularui.drawable.GuiTextures#registerIcon}! */ public Builder fluidSlot(String fluidSlotId) { return fluidSlot(fluidSlotId, 0x60FFFFFF); @@ -209,8 +267,6 @@ public Builder fluidSlot(String fluidSlotId) { /** * Set a custom fluid slot texture for UIs with this theme. - * This ID must correspond with a {@link com.cleanroommc.modularui.drawable.UITexture} that is - * registered using {@link com.cleanroommc.modularui.drawable.GuiTextures#registerIcon}! * * @param fluidSlotId The ID of the fluid slot texture * @param hoverColor The color of the tooltip hover box for this widget @@ -246,33 +302,90 @@ public Builder textField(int textColor, int markedColor) { return this; } - public Builder toggleButton(String toggleButtonId, String selectedBackgroundId) { - return toggleButton(toggleButtonId, selectedBackgroundId, 0xFFFFFFFF, true); - } - - public Builder toggleButton(String toggleButtonId, String selectedBackgroundId, int selectedColor) { - return toggleButton(toggleButtonId, selectedBackgroundId, 0xFFFFFFFF, true, null, selectedColor); - } - - public Builder toggleButton(String toggleButtonId, String selectedBackgroundId, int textColor, - boolean textShadow) { - return toggleButton(toggleButtonId, selectedBackgroundId, textColor, textShadow, null, 0xFFBBBBBB); + /** + * Set the theme options for a ToggleButton widget. + * + * @param backgroundId The main background for the unpressed button + * @param hoverBackgroundId The on-hover background for the unpressed button + * @param selectedBackgroundId The main background for the pressed button + * @param selectedHoverBackgroundId The on-hover background for the pressed button + * @param selectedColor The color to apply to the pressed button + */ + public Builder toggleButton(String backgroundId, String hoverBackgroundId, + String selectedBackgroundId, String selectedHoverBackgroundId, int selectedColor) { + return toggleButton( + backgroundId, hoverBackgroundId, + selectedBackgroundId, selectedHoverBackgroundId, + selectedColor, 0xFFBBBBBB, false); } - public Builder toggleButton(String toggleButtonId, String selectedBackgroundId, int textColor, - boolean textShadow, String selectedHoverBackgroundId, int selectedColor) { + /** + * Set the theme options for a ToggleButton widget. + * + * @param backgroundId The main background for the unpressed button + * @param hoverBackgroundId The on-hover background for the unpressed button + * @param selectedBackgroundId The main background for the pressed button + * @param selectedHoverBackgroundId The on-hover background for the pressed button + * @param selectedColor The color to apply to the pressed button + * @param textColor The color for text overlaid on this button + * @param textShadow Whether to apply text shadow to text overlaid on this button + */ + public Builder toggleButton(String backgroundId, String hoverBackgroundId, + String selectedBackgroundId, String selectedHoverBackgroundId, + int selectedColor, int textColor, boolean textShadow) { theme.elementBuilder.add(b -> b .add("toggleButton", new JsonBuilder() .add("background", new JsonBuilder() .add("type", "texture") - .add("id", toggleButtonId)) - .add("textColor", textColor) - .add("textShadow", textShadow) + .add("id", backgroundId)) + .add("hoverBackground", new JsonBuilder() + .add("type", "texture") + .add("id", hoverBackgroundId)) .add("selectedBackground", new JsonBuilder() .add("type", "texture") .add("id", selectedBackgroundId)) - .add("selectedHoverBackground", selectedHoverBackgroundId) - .add("selectedColor", selectedColor))); + .add("selectedHoverBackground", new JsonBuilder() + .add("type", "texture") + .add("id", selectedHoverBackgroundId)) + .add("selectedColor", selectedColor) + .add("textColor", textColor) + .add("textShadow", textShadow))); + return this; + } + + /** + * Simple toggle button configuration for when you want a button with no texture changes on hover. + * + * @param backgroundId The unselected background texture + * @param selectedBackgroundId The selected background texture + * @param selectedColor The background color when the button is selected + */ + public Builder simpleToggleButton(String backgroundId, String selectedBackgroundId, int selectedColor) { + return simpleToggleButton(backgroundId, selectedBackgroundId, selectedColor, 0xFFBBBBBB, false); + } + + /** + * Simple toggle button configuration for when you want a button with no texture changes on hover. + * + * @param backgroundId The unselected background texture + * @param selectedBackgroundId The selected background texture + * @param selectedColor The background color when the button is selected + * @param textColor The color for text overlaid on this button + * @param textShadow Whether to apply text shadow to text overlaid on this button + */ + public Builder simpleToggleButton(String backgroundId, String selectedBackgroundId, int selectedColor, + int textColor, boolean textShadow) { + return toggleButton( + backgroundId, backgroundId, + selectedBackgroundId, selectedBackgroundId, + selectedColor, textColor, textShadow); + } + + /** + * Set a logo supplier for this theme. + */ + public Builder logo(Supplier logo) { + theme.logo = logo; return this; } diff --git a/src/main/java/gregtech/api/mui/GTGuis.java b/src/main/java/gregtech/api/mui/GTGuis.java index 35893a3908e..d60b66088c7 100644 --- a/src/main/java/gregtech/api/mui/GTGuis.java +++ b/src/main/java/gregtech/api/mui/GTGuis.java @@ -1,77 +1,28 @@ package gregtech.api.mui; -import gregtech.api.capability.GregtechTileCapabilities; import gregtech.api.cover.Cover; -import gregtech.api.cover.CoverHolder; -import gregtech.api.cover.CoverWithUI; import gregtech.api.items.metaitem.MetaItem; import gregtech.api.metatileentity.MetaTileEntity; -import gregtech.api.util.GTUtility; +import gregtech.api.mui.factory.CoverGuiFactory; +import gregtech.api.mui.factory.MetaItemGuiFactory; +import gregtech.api.mui.factory.MetaTileEntityGuiFactory; import net.minecraft.item.ItemStack; -import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.EnumFacing; -import net.minecraft.util.EnumHand; -import com.cleanroommc.modularui.api.IGuiHolder; -import com.cleanroommc.modularui.manager.GuiInfo; +import com.cleanroommc.modularui.factory.GuiManager; import com.cleanroommc.modularui.screen.ModularPanel; - -import java.util.EnumMap; +import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.widgets.ButtonWidget; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; public class GTGuis { - private static final EnumMap COVERS = new EnumMap<>(EnumFacing.class); - - public static final GuiInfo MTE = GuiInfo.builder() - .clientGui((context, mainPanel) -> { - MetaTileEntity mte = GTUtility.getMetaTileEntity(context.getWorld(), context.getBlockPos()); - if (mte != null) { - return mte.createScreen(context, mainPanel); - } - throw new UnsupportedOperationException(); - }) - .commonGui((context, syncHandler) -> { - MetaTileEntity mte = GTUtility.getMetaTileEntity(context.getWorld(), context.getBlockPos()); - if (mte != null) { - return mte.buildUI(context, syncHandler, context.getWorld().isRemote); - } - throw new UnsupportedOperationException(); - }) - .build(); - - public static final GuiInfo PLAYER_META_ITEM_MAIN_HAND = GuiInfo.builder() - .clientGui((context, mainPanel) -> { - ItemStack itemStack = context.getMainHandItem(); - return getGuiHolder(itemStack).createScreen(context.with(EnumHand.MAIN_HAND), mainPanel); - - }) - .commonGui((context, guiSyncHandler) -> { - ItemStack itemStack = context.getMainHandItem(); - return getGuiHolder(itemStack).buildUI(context.with(EnumHand.MAIN_HAND), guiSyncHandler, - context.getWorld().isRemote); - }) - .build(); - - public static final GuiInfo PLAYER_META_ITEM_OFF_HAND = GuiInfo.builder() - .clientGui((context, mainPanel) -> { - ItemStack itemStack = context.getOffHandItem(); - return getGuiHolder(itemStack).createScreen(context.with(EnumHand.OFF_HAND), mainPanel); - - }) - .commonGui((context, guiSyncHandler) -> { - ItemStack itemStack = context.getOffHandItem(); - return getGuiHolder(itemStack).buildUI(context.with(EnumHand.OFF_HAND), guiSyncHandler, - context.getWorld().isRemote); - }) - .build(); - - public static GuiInfo getMetaItemUiInfo(EnumHand hand) { - return hand == EnumHand.MAIN_HAND ? PLAYER_META_ITEM_MAIN_HAND : PLAYER_META_ITEM_OFF_HAND; - } - - public static GuiInfo getCoverUiInfo(EnumFacing facing) { - return COVERS.get(facing); + @ApiStatus.Internal + public static void registerFactories() { + GuiManager.registerFactory(MetaTileEntityGuiFactory.INSTANCE); + GuiManager.registerFactory(MetaItemGuiFactory.INSTANCE); + GuiManager.registerFactory(CoverGuiFactory.INSTANCE); } public static ModularPanel createPanel(String name, int width, int height) { @@ -92,44 +43,40 @@ public static ModularPanel createPanel(ItemStack stack, int width, int height) { return createPanel(valueItem.unlocalizedName, width, height); } - static { - for (EnumFacing facing : EnumFacing.values()) { - COVERS.put(facing, makeCoverUiInfo(facing)); - } + public static ModularPanel createPopupPanel(String name, int width, int height) { + return createPopupPanel(name, width, height, false, false); } - private static GuiInfo makeCoverUiInfo(EnumFacing facing) { - return GuiInfo.builder() - .clientGui((context, mainPanel) -> { - TileEntity te = context.getTileEntity(); - if (te == null) throw new IllegalStateException(); - CoverHolder coverHolder = te.getCapability(GregtechTileCapabilities.CAPABILITY_COVER_HOLDER, - facing); - if (coverHolder == null) throw new IllegalStateException(); - Cover cover = coverHolder.getCoverAtSide(facing); - if (!(cover instanceof CoverWithUI)) throw new IllegalStateException(); - return ((CoverWithUI) cover).createScreen(context, mainPanel); - }) - .commonGui((context, syncHandler) -> { - TileEntity te = context.getTileEntity(); - if (te == null) throw new IllegalStateException(); - CoverHolder coverHolder = te.getCapability(GregtechTileCapabilities.CAPABILITY_COVER_HOLDER, - facing); - if (coverHolder == null) throw new IllegalStateException(); - Cover cover = coverHolder.getCoverAtSide(facing); - if (!(cover instanceof CoverWithUI)) throw new IllegalStateException(); - return ((CoverWithUI) cover).buildUI(context, syncHandler, context.getWorld().isRemote); - }) - .build(); + public static ModularPanel createPopupPanel(String name, int width, int height, boolean disableBelow, + boolean closeOnOutsideClick) { + return new PopupPanel(name, width, height, disableBelow, closeOnOutsideClick); } - private static IGuiHolder getGuiHolder(ItemStack stack) { - if (stack.getItem() instanceof MetaItem) { - MetaItem.MetaValueItem valueItem = ((MetaItem) stack.getItem()).getItem(stack); - if (valueItem != null && valueItem.getUIManager() != null) { - return valueItem.getUIManager(); - } + private static class PopupPanel extends ModularPanel { + + private final boolean disableBelow; + private final boolean closeOnOutsideClick; + + public PopupPanel(@NotNull String name, int width, int height, boolean disableBelow, + boolean closeOnOutsideClick) { + super(name); + flex().startDefaultMode(); + flex().size(width, height).align(Alignment.Center); + flex().endDefaultMode(); + background(GTGuiTextures.BACKGROUND_POPUP); + child(ButtonWidget.panelCloseButton().top(5).right(5)); + this.disableBelow = disableBelow; + this.closeOnOutsideClick = closeOnOutsideClick; + } + + @Override + public boolean disablePanelsBelow() { + return disableBelow; + } + + @Override + public boolean closeOnOutOfBoundsClick() { + return closeOnOutsideClick; } - throw new IllegalStateException(); } } diff --git a/src/main/java/gregtech/api/mui/factory/CoverGuiFactory.java b/src/main/java/gregtech/api/mui/factory/CoverGuiFactory.java new file mode 100644 index 00000000000..918aad93833 --- /dev/null +++ b/src/main/java/gregtech/api/mui/factory/CoverGuiFactory.java @@ -0,0 +1,79 @@ +package gregtech.api.mui.factory; + +import gregtech.api.capability.GregtechTileCapabilities; +import gregtech.api.cover.Cover; +import gregtech.api.cover.CoverHolder; +import gregtech.api.cover.CoverWithUI; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; + +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.AbstractUIFactory; +import com.cleanroommc.modularui.factory.GuiManager; +import com.cleanroommc.modularui.factory.SidedPosGuiData; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class CoverGuiFactory extends AbstractUIFactory { + + public static final CoverGuiFactory INSTANCE = new CoverGuiFactory(); + + private CoverGuiFactory() { + super("gregtech:cover"); + } + + public static > void open(EntityPlayer player, T cover) { + Objects.requireNonNull(player); + Objects.requireNonNull(cover); + if (!cover.getCoverableView().isValid()) { + throw new IllegalArgumentException("Can't open Cover GUI on invalid cover holder!"); + } + if (player.world != cover.getWorld()) { + throw new IllegalArgumentException("Cover must be in same dimension as the player!"); + } + BlockPos pos = cover.getPos(); + SidedPosGuiData data = new SidedPosGuiData(player, pos.getX(), pos.getY(), pos.getZ(), cover.getAttachedSide()); + GuiManager.open(INSTANCE, data, (EntityPlayerMP) player); + } + + @Override + public @NotNull IGuiHolder getGuiHolder(SidedPosGuiData data) { + TileEntity te = data.getTileEntity(); + if (te == null) { + throw new IllegalStateException("Could not get gui for null TileEntity!"); + } + CoverHolder coverHolder = te.getCapability(GregtechTileCapabilities.CAPABILITY_COVER_HOLDER, data.getSide()); + if (coverHolder == null) { + throw new IllegalStateException("Could not get CoverHolder for found TileEntity!"); + } + Cover cover = coverHolder.getCoverAtSide(data.getSide()); + if (cover == null) { + throw new IllegalStateException("Could not find cover at side " + data.getSide() + + " for found CoverHolder!"); + } + if (!(cover instanceof CoverWithUI coverWithUI)) { + throw new IllegalStateException("Cover at side " + data.getSide() + " is not a gui holder!"); + } + return coverWithUI; + } + + @Override + public void writeGuiData(SidedPosGuiData guiData, PacketBuffer buffer) { + buffer.writeVarInt(guiData.getX()); + buffer.writeVarInt(guiData.getY()); + buffer.writeVarInt(guiData.getZ()); + buffer.writeByte(guiData.getSide().getIndex()); + } + + @Override + public @NotNull SidedPosGuiData readGuiData(EntityPlayer player, PacketBuffer buffer) { + return new SidedPosGuiData(player, buffer.readVarInt(), buffer.readVarInt(), buffer.readVarInt(), + EnumFacing.VALUES[buffer.readByte()]); + } +} diff --git a/src/main/java/gregtech/api/mui/factory/MetaItemGuiFactory.java b/src/main/java/gregtech/api/mui/factory/MetaItemGuiFactory.java new file mode 100644 index 00000000000..7fea957a5a1 --- /dev/null +++ b/src/main/java/gregtech/api/mui/factory/MetaItemGuiFactory.java @@ -0,0 +1,56 @@ +package gregtech.api.mui.factory; + +import gregtech.api.items.metaitem.MetaItem; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.EnumHand; + +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.AbstractUIFactory; +import com.cleanroommc.modularui.factory.GuiManager; +import com.cleanroommc.modularui.factory.HandGuiData; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class MetaItemGuiFactory extends AbstractUIFactory { + + public static final MetaItemGuiFactory INSTANCE = new MetaItemGuiFactory(); + + private MetaItemGuiFactory() { + super("gregtech:meta_item"); + } + + public static void open(EntityPlayer player, EnumHand hand) { + Objects.requireNonNull(player); + Objects.requireNonNull(hand); + HandGuiData guiData = new HandGuiData(player, hand); + GuiManager.open(INSTANCE, guiData, (EntityPlayerMP) player); + } + + @Override + public @NotNull IGuiHolder getGuiHolder(HandGuiData data) { + ItemStack stack = data.getUsedItemStack(); + if (!(stack.getItem() instanceof MetaItemmetaItem)) { + throw new IllegalArgumentException("Found item is not a valid MetaItem!"); + } + MetaItem.MetaValueItem valueItem = metaItem.getItem(stack); + if (valueItem == null || valueItem.getUIManager() == null) { + throw new IllegalArgumentException("Found MetaItem is not a gui holder!"); + } + return valueItem.getUIManager(); + } + + @Override + public void writeGuiData(HandGuiData guiData, PacketBuffer buffer) { + buffer.writeByte(guiData.getHand().ordinal()); + } + + @Override + public @NotNull HandGuiData readGuiData(EntityPlayer player, PacketBuffer buffer) { + return new HandGuiData(player, EnumHand.values()[buffer.readByte()]); + } +} diff --git a/src/main/java/gregtech/api/mui/factory/MetaTileEntityGuiFactory.java b/src/main/java/gregtech/api/mui/factory/MetaTileEntityGuiFactory.java new file mode 100644 index 00000000000..0363476a987 --- /dev/null +++ b/src/main/java/gregtech/api/mui/factory/MetaTileEntityGuiFactory.java @@ -0,0 +1,63 @@ +package gregtech.api.mui.factory; + +import gregtech.api.metatileentity.MetaTileEntity; +import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.PacketBuffer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; + +import com.cleanroommc.modularui.api.IGuiHolder; +import com.cleanroommc.modularui.factory.AbstractUIFactory; +import com.cleanroommc.modularui.factory.GuiManager; +import com.cleanroommc.modularui.factory.PosGuiData; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class MetaTileEntityGuiFactory extends AbstractUIFactory { + + public static final MetaTileEntityGuiFactory INSTANCE = new MetaTileEntityGuiFactory(); + + private MetaTileEntityGuiFactory() { + super("gregtech:mte"); + } + + public static > void open(EntityPlayer player, T mte) { + Objects.requireNonNull(player); + Objects.requireNonNull(mte); + if (!mte.isValid()) { + throw new IllegalArgumentException("Can't open invalid MetaTileEntity GUI!"); + } + if (player.world != mte.getWorld()) { + throw new IllegalArgumentException("MetaTileEntity must be in same dimension as the player!"); + } + BlockPos pos = mte.getPos(); + PosGuiData data = new PosGuiData(player, pos.getX(), pos.getY(), pos.getZ()); + GuiManager.open(INSTANCE, data, (EntityPlayerMP) player); + } + + @Override + public @NotNull IGuiHolder getGuiHolder(PosGuiData data) { + TileEntity te = data.getTileEntity(); + if (te instanceof IGregTechTileEntity gtte) { + MetaTileEntity mte = gtte.getMetaTileEntity(); + return Objects.requireNonNull(castGuiHolder(mte), "Found MetaTileEntity is not a gui holder!"); + } + throw new IllegalStateException("Found TileEntity is not a MetaTileEntity!"); + } + + @Override + public void writeGuiData(PosGuiData guiData, PacketBuffer buffer) { + buffer.writeVarInt(guiData.getX()); + buffer.writeVarInt(guiData.getY()); + buffer.writeVarInt(guiData.getZ()); + } + + @Override + public @NotNull PosGuiData readGuiData(EntityPlayer player, PacketBuffer buffer) { + return new PosGuiData(player, buffer.readVarInt(), buffer.readVarInt(), buffer.readVarInt()); + } +} diff --git a/src/main/java/gregtech/api/mui/widget/GhostCircuitSlotWidget.java b/src/main/java/gregtech/api/mui/widget/GhostCircuitSlotWidget.java index bab12d1013b..bd848099117 100644 --- a/src/main/java/gregtech/api/mui/widget/GhostCircuitSlotWidget.java +++ b/src/main/java/gregtech/api/mui/widget/GhostCircuitSlotWidget.java @@ -114,6 +114,7 @@ private void createSelectorPanel() { .size(18) .background(GTGuiTextures.SLOT, new ItemDrawable( IntCircuitIngredient.getIntegratedCircuit(index)).asIcon()) + .disableHoverBackground() .onMousePressed(mouseButton -> { getSyncHandler().syncToServer(SYNC_CIRCUIT_INDEX, buf -> buf.writeShort(index)); circuitPreview.setItem(IntCircuitIngredient.getIntegratedCircuit(index)); @@ -122,7 +123,7 @@ private void createSelectorPanel() { } } - getPanel().getScreen().openPanel(GTGuis.createPanel("circuit_selector", 176, 120) + getPanel().getScreen().openPanel(GTGuis.createPopupPanel("circuit_selector", 176, 120) .child(IKey.lang("metaitem.circuit.integrated.gui").asWidget().pos(5, 5)) .child(circuitPreview.asIcon().size(16).asWidget() .size(18) diff --git a/src/main/java/gregtech/api/pipenet/PipeNet.java b/src/main/java/gregtech/api/pipenet/PipeNet.java index 384d15134a1..536b2a23b81 100644 --- a/src/main/java/gregtech/api/pipenet/PipeNet.java +++ b/src/main/java/gregtech/api/pipenet/PipeNet.java @@ -15,8 +15,13 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.util.*; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; public abstract class PipeNet implements INBTSerializable { @@ -63,6 +68,11 @@ public void onPipeConnectionsUpdate() {} public void onNeighbourUpdate(BlockPos fromPos) {} + /** + * Is called when any Pipe TE in the PipeNet is unloaded + */ + public void onChunkUnload() {} + public Map> getAllNodes() { return unmodifiableNodeByBlockPos; } diff --git a/src/main/java/gregtech/api/pipenet/WorldPipeNet.java b/src/main/java/gregtech/api/pipenet/WorldPipeNet.java index 23860461888..03211cc377c 100644 --- a/src/main/java/gregtech/api/pipenet/WorldPipeNet.java +++ b/src/main/java/gregtech/api/pipenet/WorldPipeNet.java @@ -1,5 +1,7 @@ package gregtech.api.pipenet; +import gregtech.api.util.GTLog; + import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.EnumFacing; @@ -12,7 +14,11 @@ import org.jetbrains.annotations.NotNull; import java.lang.ref.WeakReference; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public abstract class WorldPipeNet> extends WorldSavedData { @@ -35,9 +41,16 @@ protected void setWorldAndInit(World world) { } } - public static String getDataID(final String baseID, final World world) { - if (world == null || world.isRemote) - throw new RuntimeException("WorldPipeNet should only be created on the server!"); + public static @NotNull String getDataID(@NotNull final String baseID, @NotNull final World world) { + // noinspection ConstantValue + if (world == null || world.isRemote) { + GTLog.logger.error("WorldPipeNet should only be created on the server!", new Throwable()); + // noinspection ConstantValue + if (world == null) { + return baseID; + } + } + int dimension = world.provider.getDimension(); return dimension == 0 ? baseID : baseID + '.' + dimension; } diff --git a/src/main/java/gregtech/api/pipenet/block/BlockPipe.java b/src/main/java/gregtech/api/pipenet/block/BlockPipe.java index 5468aae38dd..667b67fcf52 100644 --- a/src/main/java/gregtech/api/pipenet/block/BlockPipe.java +++ b/src/main/java/gregtech/api/pipenet/block/BlockPipe.java @@ -34,7 +34,12 @@ import net.minecraft.item.EnumDyeColor; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.*; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.SoundCategory; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.RayTraceResult; @@ -485,7 +490,7 @@ public void harvestBlock(@NotNull World worldIn, @NotNull EntityPlayer player, @ @NotNull IBlockState state, @Nullable TileEntity te, @NotNull ItemStack stack) { tileEntities.set(te == null ? tileEntities.get() : (IPipeTile) te); super.harvestBlock(worldIn, player, pos, state, te, stack); - tileEntities.set(null); + tileEntities.remove(); } @Override diff --git a/src/main/java/gregtech/api/pipenet/longdist/BlockLongDistancePipe.java b/src/main/java/gregtech/api/pipenet/longdist/BlockLongDistancePipe.java index cb3a9a5abbb..2372a9792c8 100644 --- a/src/main/java/gregtech/api/pipenet/longdist/BlockLongDistancePipe.java +++ b/src/main/java/gregtech/api/pipenet/longdist/BlockLongDistancePipe.java @@ -1,7 +1,7 @@ package gregtech.api.pipenet.longdist; -import gregtech.api.GregTechAPI; import gregtech.api.items.toolitem.ToolClasses; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.Block; import net.minecraft.block.material.Material; @@ -33,8 +33,10 @@ public BlockLongDistancePipe(LongDistancePipeType pipeType) { super(Material.IRON); this.pipeType = pipeType; setTranslationKey("long_distance_" + pipeType.getName() + "_pipeline"); - setCreativeTab(GregTechAPI.TAB_GREGTECH); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH); setHarvestLevel(ToolClasses.WRENCH, 1); + setHardness(2f); + setResistance(10f); } @Override @@ -101,7 +103,7 @@ public void breakBlock(@NotNull World worldIn, @NotNull BlockPos pos, @NotNull I @Override public void getSubBlocks(@NotNull CreativeTabs itemIn, @NotNull NonNullList items) { - if (itemIn == GregTechAPI.TAB_GREGTECH) { + if (itemIn == GTCreativeTabs.TAB_GREGTECH) { items.add(new ItemStack(this)); } } diff --git a/src/main/java/gregtech/api/pipenet/tile/PipeCoverableImplementation.java b/src/main/java/gregtech/api/pipenet/tile/PipeCoverableImplementation.java index 6be552bfc95..df20bd54bfb 100644 --- a/src/main/java/gregtech/api/pipenet/tile/PipeCoverableImplementation.java +++ b/src/main/java/gregtech/api/pipenet/tile/PipeCoverableImplementation.java @@ -85,7 +85,8 @@ public final void removeCover(@NotNull EnumFacing side) { } @SuppressWarnings("unchecked") - public ItemStack getStackForm() { + @Override + public @NotNull ItemStack getStackForm() { BlockPipe pipeBlock = holder.getPipeBlock(); return pipeBlock.getDropItem(holder); } diff --git a/src/main/java/gregtech/api/pipenet/tile/TileEntityPipeBase.java b/src/main/java/gregtech/api/pipenet/tile/TileEntityPipeBase.java index a2d593bb206..7d4fc9015ef 100644 --- a/src/main/java/gregtech/api/pipenet/tile/TileEntityPipeBase.java +++ b/src/main/java/gregtech/api/pipenet/tile/TileEntityPipeBase.java @@ -26,6 +26,7 @@ import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.Constants.NBT; +import org.jetbrains.annotations.MustBeInvokedByOverriders; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -334,24 +335,28 @@ public T getCapabilityInternal(Capability capability, @Nullable EnumFacin @Nullable @Override public final T getCapability(@NotNull Capability capability, @Nullable EnumFacing facing) { - boolean isCoverable = capability == GregtechTileCapabilities.CAPABILITY_COVER_HOLDER; - Cover cover = facing == null ? null : coverableImplementation.getCoverAtSide(facing); - T defaultValue; - if (getPipeBlock() == null) - defaultValue = null; - else - defaultValue = getCapabilityInternal(capability, facing); + T pipeCapability = getPipeBlock() == null ? null : getCapabilityInternal(capability, facing); - if (isCoverable) { - return defaultValue; + if (capability == GregtechTileCapabilities.CAPABILITY_COVER_HOLDER) { + return pipeCapability; } - if (cover == null && facing != null) { - return isConnected(facing) ? defaultValue : null; + + Cover cover = facing == null ? null : coverableImplementation.getCoverAtSide(facing); + if (cover == null) { + if (facing == null || isConnected(facing)) { + return pipeCapability; + } + return null; } - if (cover != null) { - return cover.getCapability(capability, defaultValue); + + T coverCapability = cover.getCapability(capability, pipeCapability); + if (coverCapability == pipeCapability) { + if (isConnected(facing)) { + return pipeCapability; + } + return null; } - return defaultValue; + return coverCapability; } @Override @@ -539,6 +544,19 @@ public boolean shouldRefresh(@NotNull World world, @NotNull BlockPos pos, IBlock return oldState.getBlock() != newSate.getBlock(); } + @MustBeInvokedByOverriders + @Override + public void onChunkUnload() { + super.onChunkUnload(); + if (!world.isRemote) { + WorldPipeNet worldPipeNet = getPipeBlock().getWorldPipeNet(getWorld()); + PipeNet net = worldPipeNet.getNetFromPos(pos); + if (net != null) { + net.onChunkUnload(); + } + } + } + public void doExplosion(float explosionPower) { getWorld().setBlockToAir(getPos()); if (!getWorld().isRemote) { diff --git a/src/main/java/gregtech/api/recipes/ModHandler.java b/src/main/java/gregtech/api/recipes/ModHandler.java index 010d1c22346..1fe7ce05f07 100644 --- a/src/main/java/gregtech/api/recipes/ModHandler.java +++ b/src/main/java/gregtech/api/recipes/ModHandler.java @@ -16,6 +16,7 @@ import gregtech.api.util.DummyContainer; import gregtech.api.util.GTLog; import gregtech.api.util.LocalizationUtils; +import gregtech.api.util.Mods; import gregtech.api.util.world.DummyWorld; import gregtech.common.ConfigHolder; import gregtech.common.crafting.FluidReplaceRecipe; @@ -679,8 +680,6 @@ public static Pair getRecipeOutput(@Nullable World world, @N * disable the config and remove the recipes manually */ public static void removeSmeltingEBFMetals() { - boolean isCTLoaded = Loader.isModLoaded(GTValues.MODID_CT); - Field actionAddFurnaceRecipe$output = null; Map furnaceList = FurnaceRecipes.instance().getSmeltingList(); @@ -702,7 +701,7 @@ public static void removeSmeltingEBFMetals() { ItemStack ingot = OreDictUnifier.get(OrePrefix.ingot, material); // Check if the inputs are actually dust -> ingot if (ingot.isItemEqual(output) && dust.isItemEqual(input)) { - if (isCTLoaded) { + if (Mods.CraftTweaker.isModLoaded()) { if (actionAddFurnaceRecipe$output == null) { try { actionAddFurnaceRecipe$output = ActionAddFurnaceRecipe.class diff --git a/src/main/java/gregtech/api/recipes/Recipe.java b/src/main/java/gregtech/api/recipes/Recipe.java index b291f994301..d52be1bac09 100644 --- a/src/main/java/gregtech/api/recipes/Recipe.java +++ b/src/main/java/gregtech/api/recipes/Recipe.java @@ -29,7 +29,13 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * Class that represent machine recipe. @@ -185,8 +191,54 @@ public static Recipe trimRecipeOutputs(Recipe currentRecipe, RecipeMap recipe public final boolean matches(boolean consumeIfSuccessful, IItemHandlerModifiable inputs, IMultipleTankHandler fluidInputs) { - return matches(consumeIfSuccessful, GTUtility.itemHandlerToList(inputs), - GTUtility.fluidHandlerToList(fluidInputs)); + Pair fluids = null; + Pair items = null; + + if (fluidInputs.getFluidTanks().size() > 0) { + fluids = matchesFluid(GTUtility.fluidHandlerToList(fluidInputs)); + if (!fluids.getKey()) { + return false; + } + } + + if (inputs.getSlots() > 0) { + items = matchesItems(GTUtility.itemHandlerToList(inputs)); + if (!items.getKey()) { + return false; + } + } + + if (consumeIfSuccessful) { + if (fluids != null) { + int[] fluidAmountInTank = fluids.getValue(); + var backedList = fluidInputs.getFluidTanks(); + + for (int i = 0; i < fluidAmountInTank.length; i++) { + var tank = backedList.get(i); + FluidStack fluidStack = tank.getFluid(); + int fluidAmount = fluidAmountInTank[i]; + + if (fluidStack == null || fluidStack.amount == fluidAmount) { + continue; + } + tank.drain(Math.abs(fluidAmount - fluidStack.amount), true); + } + } + if (items != null) { + int[] itemAmountInSlot = items.getValue(); + for (int i = 0; i < itemAmountInSlot.length; i++) { + ItemStack itemInSlot = inputs.getStackInSlot(i); + int itemAmount = itemAmountInSlot[i]; + + if (itemInSlot.isEmpty() || itemInSlot.getCount() == itemAmount) { + continue; + } + inputs.extractItem(i, Math.abs(itemAmount - itemInSlot.getCount()), false); + } + } + } + + return true; } /** @@ -673,8 +725,9 @@ public boolean hasValidInputsForDisplay() { .anyMatch(s -> !s.isEmpty())) { return true; } + } else if (Arrays.stream(ingredient.getInputStacks()).anyMatch(s -> !s.isEmpty())) { + return true; } - return Arrays.stream(ingredient.getInputStacks()).anyMatch(s -> !s.isEmpty()); } for (GTRecipeInput fluidInput : fluidInputs) { FluidStack fluidIngredient = fluidInput.getInputFluidStack(); diff --git a/src/main/java/gregtech/api/recipes/RecipeBuildAction.java b/src/main/java/gregtech/api/recipes/RecipeBuildAction.java new file mode 100644 index 00000000000..f7310129276 --- /dev/null +++ b/src/main/java/gregtech/api/recipes/RecipeBuildAction.java @@ -0,0 +1,17 @@ +package gregtech.api.recipes; + +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface RecipeBuildAction> { + + /** + * Process a RecipeBuilder to perform an action with. + *

+ * Do not call {@link RecipeBuilder#buildAndRegister()} on the passed builder. + * It is safe to do so only on other builders, i.e. created through {@link RecipeBuilder#copy()}. + * + * @param builder the builder to utilize + */ + void accept(@NotNull R builder); +} diff --git a/src/main/java/gregtech/api/recipes/RecipeBuilder.java b/src/main/java/gregtech/api/recipes/RecipeBuilder.java index dd5fee598df..74357bd8130 100644 --- a/src/main/java/gregtech/api/recipes/RecipeBuilder.java +++ b/src/main/java/gregtech/api/recipes/RecipeBuilder.java @@ -9,10 +9,15 @@ import gregtech.api.recipes.chance.output.ChancedOutputLogic; import gregtech.api.recipes.chance.output.impl.ChancedFluidOutput; import gregtech.api.recipes.chance.output.impl.ChancedItemOutput; -import gregtech.api.recipes.ingredients.*; +import gregtech.api.recipes.ingredients.GTRecipeFluidInput; +import gregtech.api.recipes.ingredients.GTRecipeInput; +import gregtech.api.recipes.ingredients.GTRecipeItemInput; +import gregtech.api.recipes.ingredients.GTRecipeOreInput; +import gregtech.api.recipes.ingredients.IntCircuitIngredient; import gregtech.api.recipes.ingredients.nbtmatch.NBTCondition; import gregtech.api.recipes.ingredients.nbtmatch.NBTMatcher; import gregtech.api.recipes.recipeproperties.CleanroomProperty; +import gregtech.api.recipes.recipeproperties.DimensionProperty; import gregtech.api.recipes.recipeproperties.IRecipePropertyStorage; import gregtech.api.recipes.recipeproperties.RecipeProperty; import gregtech.api.recipes.recipeproperties.RecipePropertyStorage; @@ -22,6 +27,7 @@ import gregtech.api.util.EnumValidationResult; import gregtech.api.util.GTLog; import gregtech.api.util.GTUtility; +import gregtech.api.util.Mods; import gregtech.api.util.ValidationResult; import gregtech.common.ConfigHolder; import gregtech.integration.groovy.GroovyScriptModule; @@ -38,12 +44,19 @@ import com.cleanroommc.groovyscript.api.IIngredient; import com.cleanroommc.groovyscript.helper.ingredient.OreDictIngredient; import crafttweaker.CraftTweakerAPI; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; import org.apache.commons.lang3.builder.ToStringBuilder; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.MustBeInvokedByOverriders; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.function.Consumer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; /** * @see Recipe @@ -70,9 +83,8 @@ public class RecipeBuilder> { protected GTRecipeCategory category; protected boolean isCTRecipe = false; protected int parallel = 0; - protected Consumer onBuildAction = null; protected EnumValidationResult recipeStatus = EnumValidationResult.VALID; - protected IRecipePropertyStorage recipePropertyStorage = null; + protected @Nullable IRecipePropertyStorage recipePropertyStorage = null; protected boolean recipePropertyStorageErrored = false; protected RecipeBuilder() { @@ -121,8 +133,8 @@ protected RecipeBuilder(RecipeBuilder recipeBuilder) { this.EUt = recipeBuilder.EUt; this.hidden = recipeBuilder.hidden; this.category = recipeBuilder.category; - this.onBuildAction = recipeBuilder.onBuildAction; - this.recipePropertyStorage = recipeBuilder.recipePropertyStorage; + this.recipePropertyStorage = recipeBuilder.recipePropertyStorage == null ? null : + recipeBuilder.recipePropertyStorage.copy(); if (this.recipePropertyStorage != null) { this.recipePropertyStorage = this.recipePropertyStorage.copy(); } @@ -136,8 +148,51 @@ public R cleanroom(@Nullable CleanroomType cleanroom) { return (R) this; } + public R dimension(int dimensionID) { + return dimension(dimensionID, false); + } + + public R dimension(int dimensionID, boolean toBlackList) { + DimensionProperty.DimensionPropertyList dimensionIDs = getCompleteDimensionIDs(); + if (dimensionIDs == DimensionProperty.DimensionPropertyList.EMPTY_LIST) { + dimensionIDs = new DimensionProperty.DimensionPropertyList(); + this.applyProperty(DimensionProperty.getInstance(), dimensionIDs); + } + dimensionIDs.add(dimensionID, toBlackList); + return (R) this; + } + + public DimensionProperty.DimensionPropertyList getCompleteDimensionIDs() { + return this.recipePropertyStorage == null ? DimensionProperty.DimensionPropertyList.EMPTY_LIST : + this.recipePropertyStorage.getRecipePropertyValue(DimensionProperty.getInstance(), + DimensionProperty.DimensionPropertyList.EMPTY_LIST); + } + + public IntList getDimensionIDs() { + return this.recipePropertyStorage == null ? IntLists.EMPTY_LIST : + this.recipePropertyStorage.getRecipePropertyValue(DimensionProperty.getInstance(), + DimensionProperty.DimensionPropertyList.EMPTY_LIST).whiteListDimensions; + } + + public IntList getBlockedDimensionIDs() { + return this.recipePropertyStorage == null ? IntLists.EMPTY_LIST : + this.recipePropertyStorage.getRecipePropertyValue(DimensionProperty.getInstance(), + DimensionProperty.DimensionPropertyList.EMPTY_LIST).whiteListDimensions; + } + public boolean applyProperty(@NotNull String key, @Nullable Object value) { - if (key.equals(CleanroomProperty.KEY)) { + if (key.equals(DimensionProperty.KEY)) { + if (value instanceof DimensionProperty.DimensionPropertyList list) { + DimensionProperty.DimensionPropertyList dimensionIDs = getCompleteDimensionIDs(); + if (dimensionIDs == DimensionProperty.DimensionPropertyList.EMPTY_LIST) { + dimensionIDs = new DimensionProperty.DimensionPropertyList(); + this.applyProperty(DimensionProperty.getInstance(), dimensionIDs); + } + dimensionIDs.merge(list); + return true; + } + return false; + } else if (key.equals(CleanroomProperty.KEY)) { if (value instanceof CleanroomType) { this.cleanroom((CleanroomType) value); } else if (value instanceof String) { @@ -275,20 +330,20 @@ public R inputNBT(OrePrefix orePrefix, Material material, int count, NBTMatcher } public R inputNBT(Item item, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(new ItemStack(item)), matcher, condition); + return inputNBT(new ItemStack(item), matcher, condition); } public R inputNBT(Item item, int count, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(new ItemStack(item), count), matcher, condition); + return inputNBT(new ItemStack(item, count), matcher, condition); } public R inputNBT(Item item, int count, int meta, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(new ItemStack(item, count, meta)), matcher, condition); + return inputNBT(new ItemStack(item, count, meta), matcher, condition); } public R inputNBT(Item item, int count, @SuppressWarnings("unused") boolean wild, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(new ItemStack(item, count, GTValues.W)), matcher, condition); + return inputNBT(new ItemStack(item, count, GTValues.W), matcher, condition); } public R inputNBT(Block block, NBTMatcher matcher, NBTCondition condition) { @@ -296,34 +351,57 @@ public R inputNBT(Block block, NBTMatcher matcher, NBTCondition condition) { } public R inputNBT(Block block, int count, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(new ItemStack(block, count)), matcher, condition); + return inputNBT(new ItemStack(block, count), matcher, condition); } public R inputNBT(Block block, int count, @SuppressWarnings("unused") boolean wild, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(new ItemStack(block, count, GTValues.W)), matcher, condition); + return inputNBT(new ItemStack(block, count, GTValues.W), matcher, condition); } public R inputNBT(MetaItem.MetaValueItem item, int count, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(item.getStackForm(count)), matcher, condition); + return inputNBT(item.getStackForm(count), matcher, condition); } public R inputNBT(MetaItem.MetaValueItem item, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(item.getStackForm()), matcher, condition); + return inputNBT(item.getStackForm(), matcher, condition); } public R inputNBT(MetaTileEntity mte, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(mte.getStackForm()), matcher, condition); + return inputNBT(mte.getStackForm(), matcher, condition); } public R inputNBT(MetaTileEntity mte, int amount, NBTMatcher matcher, NBTCondition condition) { - return inputNBT(new GTRecipeItemInput(mte.getStackForm(amount)), matcher, condition); + return inputNBT(mte.getStackForm(amount), matcher, condition); + } + + /** + * NBT tags are stripped from the input stack and are not automatically checked. + * + * @param stack the itemstack to input. + * @param matcher the matcher for the stack's nbt + * @param condition the condition for the stack's nbt + * @return this + */ + public R inputNBT(@NotNull ItemStack stack, NBTMatcher matcher, NBTCondition condition) { + return inputNBT(new GTRecipeItemInput(stack), matcher, condition); + } + + public R inputs(ItemStack input) { + if (input == null || input.isEmpty()) { + GTLog.logger.error("Input cannot be null or empty. Input: {}", input); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + recipeStatus = EnumValidationResult.INVALID; + } else { + this.inputs.add(new GTRecipeItemInput(input)); + } + return (R) this; } public R inputs(ItemStack... inputs) { for (ItemStack input : inputs) { if (input == null || input.isEmpty()) { - GTLog.logger.error("Input cannot contain null or empty ItemStacks. Inputs: {}", input); + GTLog.logger.error("Inputs cannot contain null or empty ItemStacks. Inputs: {}", input); GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); recipeStatus = EnumValidationResult.INVALID; continue; @@ -346,10 +424,21 @@ public R inputStacks(Collection inputs) { return (R) this; } + public R inputs(GTRecipeInput input) { + if (input.getAmount() < 0) { + GTLog.logger.error("Input count cannot be less than 0. Actual: {}.", input.getAmount()); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + recipeStatus = EnumValidationResult.INVALID; + } else { + this.inputs.add(input); + } + return (R) this; + } + public R inputs(GTRecipeInput... inputs) { for (GTRecipeInput input : inputs) { if (input.getAmount() < 0) { - GTLog.logger.error("Count cannot be less than 0. Actual: {}.", input.getAmount()); + GTLog.logger.error("Input count cannot be less than 0. Actual: {}.", input.getAmount()); GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); recipeStatus = EnumValidationResult.INVALID; continue; @@ -464,6 +553,13 @@ public R output(MetaTileEntity mte, int amount) { return outputs(mte.getStackForm(amount)); } + public R outputs(ItemStack output) { + if (output != null && !output.isEmpty()) { + this.outputs.add(output); + } + return (R) this; + } + public R outputs(ItemStack... outputs) { return outputs(Arrays.asList(outputs)); } @@ -490,13 +586,25 @@ public R fluidInputs(GTRecipeInput fluidIngredient) { return (R) this; } + public R fluidInputs(FluidStack input) { + if (input != null && input.amount > 0) { + this.fluidInputs.add(new GTRecipeFluidInput(input)); + } else if (input != null) { + GTLog.logger.error("Fluid Input count cannot be less than 0. Actual: {}.", input.amount); + GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); + } else { + GTLog.logger.error("FluidStack cannot be null."); + } + return (R) this; + } + public R fluidInputs(FluidStack... fluidStacks) { ArrayList fluidIngredients = new ArrayList<>(); for (FluidStack fluidStack : fluidStacks) { if (fluidStack != null && fluidStack.amount > 0) { fluidIngredients.add(new GTRecipeFluidInput(fluidStack)); } else if (fluidStack != null) { - GTLog.logger.error("Count cannot be less than 0. Actual: {}.", fluidStack.amount); + GTLog.logger.error("Fluid Input count cannot be less than 0. Actual: {}.", fluidStack.amount); GTLog.logger.error("Stacktrace:", new IllegalArgumentException()); } else { GTLog.logger.error("FluidStack cannot be null."); @@ -511,13 +619,20 @@ public R clearFluidInputs() { return (R) this; } + public R fluidOutputs(FluidStack output) { + if (output != null && output.amount > 0) { + this.fluidOutputs.add(output); + } + return (R) this; + } + public R fluidOutputs(FluidStack... outputs) { return fluidOutputs(Arrays.asList(outputs)); } public R fluidOutputs(Collection outputs) { outputs = new ArrayList<>(outputs); - outputs.removeIf(Objects::isNull); + outputs.removeIf(o -> o == null || o.amount <= 0); this.fluidOutputs.addAll(outputs); return (R) this; } @@ -607,12 +722,12 @@ public R chancedFluidOutputLogic(@NotNull ChancedOutputLogic logic) { return (R) this; } - @Optional.Method(modid = GTValues.MODID_GROOVYSCRIPT) + @Optional.Method(modid = Mods.Names.GROOVY_SCRIPT) public R inputs(IIngredient ingredient) { return input(ofGroovyIngredient(ingredient)); } - @Optional.Method(modid = GTValues.MODID_GROOVYSCRIPT) + @Optional.Method(modid = Mods.Names.GROOVY_SCRIPT) public R inputs(IIngredient... ingredients) { for (IIngredient ingredient : ingredients) { inputs(ingredient); @@ -620,7 +735,7 @@ public R inputs(IIngredient... ingredients) { return (R) this; } - @Optional.Method(modid = GTValues.MODID_GROOVYSCRIPT) + @Optional.Method(modid = Mods.Names.GROOVY_SCRIPT) public R inputs(Collection ingredients) { for (IIngredient ingredient : ingredients) { inputs(ingredient); @@ -628,12 +743,12 @@ public R inputs(Collection ingredients) { return (R) this; } - @Optional.Method(modid = GTValues.MODID_GROOVYSCRIPT) + @Optional.Method(modid = Mods.Names.GROOVY_SCRIPT) public R notConsumable(IIngredient ingredient) { return notConsumable(ofGroovyIngredient(ingredient)); } - @Optional.Method(modid = GTValues.MODID_GROOVYSCRIPT) + @Optional.Method(modid = Mods.Names.GROOVY_SCRIPT) private static GTRecipeInput ofGroovyIngredient(IIngredient ingredient) { if (ingredient instanceof OreDictIngredient) { return new GTRecipeOreInput(((OreDictIngredient) ingredient).getOreDict(), ingredient.getAmount()); @@ -857,7 +972,7 @@ protected EnumValidationResult validate() { return recipeStatus; } - @Optional.Method(modid = GTValues.MODID_GROOVYSCRIPT) + @Optional.Method(modid = Mods.Names.GROOVY_SCRIPT) protected void validateGroovy(GroovyLog.Msg errorMsg) { errorMsg.add(EUt == 0, () -> "EU/t must not be to 0"); errorMsg.add(duration <= 0, () -> "Duration must not be less or equal to 0"); @@ -886,19 +1001,26 @@ protected static String getRequiredString(int max, int actual, @NotNull String t return out; } - protected R onBuild(Consumer consumer) { - this.onBuildAction = consumer; - return (R) this; - } - + /** + * @deprecated Obsolete. Does not need calling. + */ + @ApiStatus.Obsolete + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") + @Deprecated protected R invalidateOnBuildAction() { - this.onBuildAction = null; return (R) this; } + /** + * Build and register the recipe, if valid. + * Do not call outside of the + * {@link net.minecraftforge.event.RegistryEvent.Register} event for recipes. + * + */ + @MustBeInvokedByOverriders public void buildAndRegister() { - if (onBuildAction != null) { - onBuildAction.accept((R) this); + for (RecipeBuildAction action : recipeMap.getBuildActions()) { + action.accept((R) this); } ValidationResult validationResult = build(); recipeMap.addRecipe(validationResult); @@ -975,6 +1097,8 @@ public String toString() { .append("EUt", EUt) .append("hidden", hidden) .append("cleanroom", getCleanroom()) + .append("dimensions", getDimensionIDs().toString()) + .append("dimensions_blocked", getBlockedDimensionIDs().toString()) .append("recipeStatus", recipeStatus) .toString(); } diff --git a/src/main/java/gregtech/api/recipes/RecipeMap.java b/src/main/java/gregtech/api/recipes/RecipeMap.java index 2f9417afe27..218489f1c1d 100644 --- a/src/main/java/gregtech/api/recipes/RecipeMap.java +++ b/src/main/java/gregtech/api/recipes/RecipeMap.java @@ -27,6 +27,7 @@ import gregtech.api.util.GTLog; import gregtech.api.util.GTUtility; import gregtech.api.util.LocalizationUtils; +import gregtech.api.util.Mods; import gregtech.api.util.ValidationResult; import gregtech.common.ConfigHolder; import gregtech.integration.crafttweaker.CTRecipeHelper; @@ -38,6 +39,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundEvent; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fml.common.Optional.Method; @@ -58,6 +60,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; import stanhebben.zenscript.annotations.Optional; import stanhebben.zenscript.annotations.ZenClass; import stanhebben.zenscript.annotations.ZenGetter; @@ -74,9 +77,7 @@ import java.util.Map; import java.util.Set; import java.util.WeakHashMap; -import java.util.function.Consumer; import java.util.function.DoubleSupplier; -import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -123,9 +124,9 @@ public class RecipeMap> { private final Map> recipeByCategory = new Object2ObjectOpenHashMap<>(); - private Consumer onRecipeBuildAction; - protected SoundEvent sound; - private RecipeMap smallRecipeMap; + private final Map> recipeBuildActions = new Object2ObjectOpenHashMap<>(); + protected @Nullable SoundEvent sound; + private @Nullable RecipeMap smallRecipeMap; /** * Create and register new instance of RecipeMap with specified properties. All @@ -139,7 +140,7 @@ public class RecipeMap> { * @param defaultRecipeBuilder the default RecipeBuilder for the RecipeMap * @param isHidden if the RecipeMap should have a category in JEI * - * @deprecated {@link #RecipeMap(String, RecipeBuilder, Function, int, int, int, int)} + * @deprecated {@link RecipeMap#RecipeMap(String, R, RecipeMapUIFunction, int, int, int, int)} */ @ApiStatus.ScheduledForRemoval(inVersion = "2.9") @Deprecated @@ -168,7 +169,7 @@ public RecipeMap(@NotNull String unlocalizedName, * @param defaultRecipeBuilder the default RecipeBuilder for the RecipeMap * @param isHidden if the RecipeMap should have a category in JEI * - * @deprecated {@link #RecipeMap(String, RecipeBuilder, Function, int, int, int, int)} + * @deprecated {@link RecipeMap#RecipeMap(String, R, RecipeMapUIFunction, int, int, int, int)} */ @ApiStatus.ScheduledForRemoval(inVersion = "2.9") @Deprecated @@ -310,11 +311,46 @@ public RecipeMap setChanceFunction(@NotNull ChanceBoostFunction function) { return this; } - public RecipeMap onRecipeBuild(Consumer consumer) { - onRecipeBuildAction = consumer; + /** + * Add a recipe build action to be performed upon this RecipeMap's builder's recipe registration. + * + * @param name the unique name of the action + * @param action the action to perform + * @return this + */ + public RecipeMap onRecipeBuild(@NotNull ResourceLocation name, @NotNull RecipeBuildAction action) { + if (recipeBuildActions.containsKey(name)) { + throw new IllegalArgumentException("Cannot register RecipeBuildAction with duplicate name: " + name); + } + recipeBuildActions.put(name, action); return this; } + /** + * @param name the name of the build action to remove + */ + public void removeBuildAction(@NotNull ResourceLocation name) { + recipeBuildActions.remove(name); + } + + /** + * Add a recipe build action to be performed upon this RecipeMap's builder's recipe registration. + * + * @param actions the actions to perform + */ + @ApiStatus.Internal + protected void onRecipeBuild(@NotNull Map> actions) { + recipeBuildActions.putAll(actions); + } + + /** + * @return the build actions for this RecipeMap's default RecipeBuilder + */ + @ApiStatus.Internal + protected @UnmodifiableView @NotNull Collection<@NotNull RecipeBuildAction> getBuildActions() { + return this.recipeBuildActions.values(); + } + public RecipeMap allowEmptyOutput() { this.allowEmptyOutput = true; return this; @@ -942,7 +978,7 @@ private boolean shouldShiftWidgets() { return false; } - @Method(modid = GTValues.MODID_GROOVYSCRIPT) + @Method(modid = Mods.Names.GROOVY_SCRIPT) private VirtualizedRecipeMap getGroovyScriptRecipeMap() { return ((VirtualizedRecipeMap) grsVirtualizedRecipeMap); } @@ -1291,7 +1327,7 @@ public Collection getRecipeList() { } @ZenMethod("findRecipe") - @Method(modid = GTValues.MODID_CT) + @Method(modid = Mods.Names.CRAFT_TWEAKER) @Nullable public CTRecipe ctFindRecipe(long maxVoltage, IItemStack[] itemInputs, ILiquidStack[] fluidInputs, @Optional(valueLong = Integer.MAX_VALUE) int outputFluidTankCapacity) { @@ -1304,7 +1340,7 @@ public CTRecipe ctFindRecipe(long maxVoltage, IItemStack[] itemInputs, ILiquidSt } @ZenGetter("recipes") - @Method(modid = GTValues.MODID_CT) + @Method(modid = Mods.Names.CRAFT_TWEAKER) public List ctGetRecipeList() { return getRecipeList().stream().map(recipe -> new CTRecipe(this, recipe)).collect(Collectors.toList()); } @@ -1325,7 +1361,7 @@ public String getUnlocalizedName() { } public R recipeBuilder() { - return recipeBuilderSample.copy().onBuild(onRecipeBuildAction); + return recipeBuilderSample.copy(); } /** @@ -1393,7 +1429,7 @@ public R recipeBuilder() { } @ZenMethod("recipeBuilder") - @Method(modid = GTValues.MODID_CT) + @Method(modid = Mods.Names.CRAFT_TWEAKER) public CTRecipeBuilder ctRecipeBuilder() { return new CTRecipeBuilder(recipeBuilder()); } diff --git a/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java b/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java index 2d6e810cac8..8995cf529aa 100644 --- a/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java +++ b/src/main/java/gregtech/api/recipes/RecipeMapBuilder.java @@ -5,13 +5,17 @@ import gregtech.api.recipes.ui.RecipeMapUI; import gregtech.api.recipes.ui.RecipeMapUIFunction; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundEvent; import it.unimi.dsi.fastutil.bytes.Byte2ObjectArrayMap; import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Map; + import static gregtech.api.recipes.ui.RecipeMapUI.computeOverlayKey; public class RecipeMapBuilder> { @@ -41,6 +45,8 @@ public class RecipeMapBuilder> { private SoundEvent sound; private boolean allowEmptyOutputs; + private @Nullable Map> buildActions; + /** * @param unlocalizedName the name of the recipemap * @param defaultRecipeBuilder the default recipe builder of the recipemap @@ -247,6 +253,23 @@ public RecipeMapBuilder(@NotNull String unlocalizedName, @NotNull B defaultRecip return this; } + /** + * Add a recipe build action to be performed upon this RecipeMap's builder's recipe registration. + * + * @param name the unique name of the action + * @param action the action to perform + * @return this + */ + public @NotNull RecipeMapBuilder onBuild(@NotNull ResourceLocation name, @NotNull RecipeBuildAction action) { + if (buildActions == null) { + buildActions = new Object2ObjectOpenHashMap<>(); + } else if (buildActions.containsKey(name)) { + throw new IllegalArgumentException("Cannot register RecipeBuildAction with duplicate name: " + name); + } + buildActions.put(name, action); + return this; + } + /** * Do not call this twice. RecipeMapBuilders are not re-usable. * @@ -260,6 +283,9 @@ public RecipeMapBuilder(@NotNull String unlocalizedName, @NotNull B defaultRecip if (allowEmptyOutputs) { recipeMap.allowEmptyOutput(); } + if (buildActions != null) { + recipeMap.onRecipeBuild(buildActions); + } return recipeMap; } } diff --git a/src/main/java/gregtech/api/recipes/RecipeMaps.java b/src/main/java/gregtech/api/recipes/RecipeMaps.java index e61261ddd09..b5bdde0fa94 100644 --- a/src/main/java/gregtech/api/recipes/RecipeMaps.java +++ b/src/main/java/gregtech/api/recipes/RecipeMaps.java @@ -11,7 +11,6 @@ import gregtech.api.recipes.builders.ComputationRecipeBuilder; import gregtech.api.recipes.builders.FuelRecipeBuilder; import gregtech.api.recipes.builders.FusionRecipeBuilder; -import gregtech.api.recipes.builders.GasCollectorRecipeBuilder; import gregtech.api.recipes.builders.ImplosionRecipeBuilder; import gregtech.api.recipes.builders.PrimitiveRecipeBuilder; import gregtech.api.recipes.builders.SimpleRecipeBuilder; @@ -44,6 +43,7 @@ import stanhebben.zenscript.annotations.ZenProperty; import static gregtech.api.GTValues.*; +import static gregtech.api.util.GTUtility.gregtechId; /** * Notes: @@ -123,13 +123,12 @@ public final class RecipeMaps { .fluidOutputs(1) .progressBar(GuiTextures.PROGRESS_BAR_ARC_FURNACE) .sound(GTSoundEvents.ARC) - .build() - .onRecipeBuild(recipeBuilder -> { - recipeBuilder.invalidateOnBuildAction(); + .onBuild(gregtechId("arc_furnace_oxygen"), recipeBuilder -> { if (recipeBuilder.getFluidInputs().isEmpty()) { recipeBuilder.fluidInputs(Materials.Oxygen.getFluid(recipeBuilder.getDuration())); } - }); + }) + .build(); /** * Example: @@ -152,9 +151,7 @@ public final class RecipeMaps { .itemSlotOverlay(GuiTextures.CIRCUIT_OVERLAY, false) .progressBar(GuiTextures.PROGRESS_BAR_CIRCUIT) .sound(GTSoundEvents.ASSEMBLER) - .build() - .onRecipeBuild(recipeBuilder -> { - recipeBuilder.invalidateOnBuildAction(); + .onBuild(gregtechId("assembler_solder"), recipeBuilder -> { var fluidInputs = recipeBuilder.getFluidInputs(); if (fluidInputs.size() == 1 && fluidInputs.get(0).getInputFluidStack().getFluid() == Materials.SolderingAlloy.getFluid()) { @@ -163,7 +160,8 @@ public final class RecipeMaps { recipeBuilder.copy().clearFluidInputs().fluidInputs(Materials.Tin.getFluid(amount * 2)) .buildAndRegister(); } - + }) + .onBuild(gregtechId("assembler_recycling"), recipeBuilder -> { if (recipeBuilder.isWithRecycling()) { // ignore input fluids for recycling ItemStack outputStack = recipeBuilder.getOutputs().get(0); @@ -173,7 +171,8 @@ public final class RecipeMaps { OreDictUnifier.registerOre(outputStack, info); } } - }); + }) + .build(); /** * Example: @@ -192,12 +191,14 @@ public final class RecipeMaps { * .duration(600).EUt(6000).buildAndRegister(); * * - * The Assembly Line Recipe Builder has no special properties/build actions yet, but will in the future + * The Assembly Line Recipe Builder creates additional Research Recipes for its outputs in the Scanner or Research + * Station when specified. */ @ZenProperty public static final RecipeMap ASSEMBLY_LINE_RECIPES = new RecipeMapAssemblyLine<>( "assembly_line", new AssemblyLineRecipeBuilder(), AssemblyLineUI::new) - .onRecipeBuild(AssemblyLineManager::createDefaultResearchRecipe); + .onRecipeBuild(gregtechId("default_research_recipe"), + AssemblyLineManager::createDefaultResearchRecipe); /** * Example: @@ -437,21 +438,18 @@ public final class RecipeMaps { .fluidSlotOverlay(GuiTextures.VIAL_OVERLAY_2, true) .progressBar(GuiTextures.PROGRESS_BAR_ARROW_MULTIPLE) .sound(GTValues.FOOLS.get() ? GTSoundEvents.SCIENCE : GTSoundEvents.CHEMICAL_REACTOR) - .build() - .onRecipeBuild(recipeBuilder -> { - recipeBuilder.invalidateOnBuildAction(); - RecipeMaps.LARGE_CHEMICAL_RECIPES.recipeBuilder() - .inputs(recipeBuilder.getInputs().toArray(new GTRecipeInput[0])) - .fluidInputs(recipeBuilder.getFluidInputs()) - .outputs(recipeBuilder.getOutputs()) - .chancedOutputs(recipeBuilder.getChancedOutputs()) - .fluidOutputs(recipeBuilder.getFluidOutputs()) - .chancedFluidOutputs(recipeBuilder.getChancedFluidOutputs()) - .cleanroom(recipeBuilder.getCleanroom()) - .duration(recipeBuilder.getDuration()) - .EUt(recipeBuilder.getEUt()) - .buildAndRegister(); - }); + .onBuild(gregtechId("lcr_copy"), recipeBuilder -> RecipeMaps.LARGE_CHEMICAL_RECIPES.recipeBuilder() + .inputs(recipeBuilder.getInputs().toArray(new GTRecipeInput[0])) + .fluidInputs(recipeBuilder.getFluidInputs()) + .outputs(recipeBuilder.getOutputs()) + .chancedOutputs(recipeBuilder.getChancedOutputs()) + .fluidOutputs(recipeBuilder.getFluidOutputs()) + .chancedFluidOutputs(recipeBuilder.getChancedFluidOutputs()) + .cleanroom(recipeBuilder.getCleanroom()) + .duration(recipeBuilder.getDuration()) + .EUt(recipeBuilder.getEUt()) + .buildAndRegister()) + .build(); /** * Example: @@ -489,9 +487,7 @@ public final class RecipeMaps { .itemSlotOverlay(GuiTextures.CIRCUIT_OVERLAY, false) .progressBar(GuiTextures.PROGRESS_BAR_CIRCUIT_ASSEMBLER) .sound(GTSoundEvents.ASSEMBLER) - .build() - .onRecipeBuild(recipeBuilder -> { - recipeBuilder.invalidateOnBuildAction(); + .onBuild(gregtechId("circuit_assembler_solder"), recipeBuilder -> { if (recipeBuilder.getFluidInputs().isEmpty()) { recipeBuilder.copy() .fluidInputs(Materials.SolderingAlloy.getFluid(Math.max(1, @@ -504,7 +500,8 @@ public final class RecipeMaps { recipeBuilder.fluidInputs(Materials.Tin.getFluid(Math.max(1, GTValues.L * recipeBuilder.getSolderMultiplier()))); } - }); + }) + .build(); /** * Example: @@ -612,11 +609,8 @@ public final class RecipeMaps { .itemSlotOverlay(GuiTextures.DUST_OVERLAY, true, true) .progressBar(GuiTextures.PROGRESS_BAR_SLICE) .sound(GTSoundEvents.CUT) - .build() - .onRecipeBuild(recipeBuilder -> { - recipeBuilder.invalidateOnBuildAction(); + .onBuild(gregtechId("cutter_fluid"), recipeBuilder -> { if (recipeBuilder.getFluidInputs().isEmpty()) { - int duration = recipeBuilder.getDuration(); int eut = recipeBuilder.getEUt(); recipeBuilder @@ -642,7 +636,8 @@ public final class RecipeMaps { .duration(Math.max(1, duration)); } - }); + }) + .build(); /** * Examples: @@ -982,8 +977,8 @@ public final class RecipeMaps { .build(); @ZenProperty - public static final RecipeMap GAS_COLLECTOR_RECIPES = new RecipeMapBuilder<>( - "gas_collector", new GasCollectorRecipeBuilder()) + public static final RecipeMap GAS_COLLECTOR_RECIPES = new RecipeMapBuilder<>( + "gas_collector", new SimpleRecipeBuilder()) .itemInputs(1) .fluidOutputs(1) .itemSlotOverlay(GuiTextures.INT_CIRCUIT_OVERLAY, false, true) diff --git a/src/main/java/gregtech/api/recipes/builders/AssemblyLineRecipeBuilder.java b/src/main/java/gregtech/api/recipes/builders/AssemblyLineRecipeBuilder.java index 2d9d5006c1b..9ddc9d6851b 100644 --- a/src/main/java/gregtech/api/recipes/builders/AssemblyLineRecipeBuilder.java +++ b/src/main/java/gregtech/api/recipes/builders/AssemblyLineRecipeBuilder.java @@ -155,6 +155,7 @@ public static class ResearchRecipeEntry { private final String researchId; private final ItemStack researchStack; private final ItemStack dataStack; + private final boolean ignoreNBT; private final int duration; private final int EUt; private final int CWUt; @@ -166,6 +167,9 @@ public static class ResearchRecipeEntry { * @param duration the duration of the recipe * @param EUt the EUt of the recipe * @param CWUt how much computation per tick this recipe needs if in Research Station + *

+ * By default, will ignore NBT on researchStack input. If NBT matching is desired, see + * {@link #ResearchRecipeEntry(String, ItemStack, ItemStack, boolean, int, int, int)} */ public ResearchRecipeEntry(@NotNull String researchId, @NotNull ItemStack researchStack, @NotNull ItemStack dataStack, int duration, int EUt, int CWUt) { @@ -175,6 +179,26 @@ public ResearchRecipeEntry(@NotNull String researchId, @NotNull ItemStack resear this.duration = duration; this.EUt = EUt; this.CWUt = CWUt; + this.ignoreNBT = true; + } + + /** + * @param researchId the id of the research to store + * @param researchStack the stack to scan for research + * @param dataStack the stack to contain the data + * @param duration the duration of the recipe + * @param EUt the EUt of the recipe + * @param CWUt how much computation per tick this recipe needs if in Research Station + */ + public ResearchRecipeEntry(@NotNull String researchId, @NotNull ItemStack researchStack, + @NotNull ItemStack dataStack, boolean ignoreNBT, int duration, int EUt, int CWUt) { + this.researchId = researchId; + this.researchStack = researchStack; + this.dataStack = dataStack; + this.ignoreNBT = ignoreNBT; + this.duration = duration; + this.EUt = EUt; + this.CWUt = CWUt; } @NotNull @@ -192,6 +216,10 @@ public ItemStack getDataStack() { return dataStack; } + public boolean getIgnoreNBT() { + return ignoreNBT; + } + public int getDuration() { return duration; } diff --git a/src/main/java/gregtech/api/recipes/builders/GasCollectorRecipeBuilder.java b/src/main/java/gregtech/api/recipes/builders/GasCollectorRecipeBuilder.java deleted file mode 100644 index fe3bb345305..00000000000 --- a/src/main/java/gregtech/api/recipes/builders/GasCollectorRecipeBuilder.java +++ /dev/null @@ -1,82 +0,0 @@ -package gregtech.api.recipes.builders; - -import gregtech.api.recipes.Recipe; -import gregtech.api.recipes.RecipeBuilder; -import gregtech.api.recipes.RecipeMap; -import gregtech.api.recipes.recipeproperties.GasCollectorDimensionProperty; - -import crafttweaker.CraftTweakerAPI; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.ints.IntLists; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class GasCollectorRecipeBuilder extends RecipeBuilder { - - public GasCollectorRecipeBuilder() {} - - public GasCollectorRecipeBuilder(Recipe recipe, RecipeMap recipeMap) { - super(recipe, recipeMap); - } - - public GasCollectorRecipeBuilder(RecipeBuilder recipeBuilder) { - super(recipeBuilder); - } - - @Override - public GasCollectorRecipeBuilder copy() { - return new GasCollectorRecipeBuilder(this); - } - - @Override - public boolean applyProperty(@NotNull String key, Object value) { - if (key.equals(GasCollectorDimensionProperty.KEY)) { - if (value instanceof Integer) { - this.dimension((Integer) value); - } else if (value instanceof List && !((List) value).isEmpty() && - ((List) value).get(0) instanceof Integer) { - IntList dimensionIDs = getDimensionIDs(); - if (dimensionIDs == IntLists.EMPTY_LIST) { - dimensionIDs = new IntArrayList(); - this.applyProperty(GasCollectorDimensionProperty.getInstance(), dimensionIDs); - } - dimensionIDs.addAll((List) value); - } else { - if (isCTRecipe) { - CraftTweakerAPI.logError("Dimension for Gas Collector needs to be a Integer"); - return false; - } - throw new IllegalArgumentException("Invalid Dimension Property Type!"); - } - return true; - } - return super.applyProperty(key, value); - } - - public GasCollectorRecipeBuilder dimension(int dimensionID) { - IntList dimensionIDs = getDimensionIDs(); - if (dimensionIDs == IntLists.EMPTY_LIST) { - dimensionIDs = new IntArrayList(); - this.applyProperty(GasCollectorDimensionProperty.getInstance(), dimensionIDs); - } - dimensionIDs.add(dimensionID); - return this; - } - - public IntList getDimensionIDs() { - return this.recipePropertyStorage == null ? IntLists.EMPTY_LIST : - this.recipePropertyStorage.getRecipePropertyValue(GasCollectorDimensionProperty.getInstance(), - IntLists.EMPTY_LIST); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .appendSuper(super.toString()) - .append(GasCollectorDimensionProperty.getInstance().getKey(), getDimensionIDs().toString()) - .toString(); - } -} diff --git a/src/main/java/gregtech/api/recipes/builders/ResearchRecipeBuilder.java b/src/main/java/gregtech/api/recipes/builders/ResearchRecipeBuilder.java index bbbda50d520..b20da993e70 100644 --- a/src/main/java/gregtech/api/recipes/builders/ResearchRecipeBuilder.java +++ b/src/main/java/gregtech/api/recipes/builders/ResearchRecipeBuilder.java @@ -15,12 +15,22 @@ public abstract class ResearchRecipeBuilder> protected ItemStack researchStack; protected ItemStack dataStack; + protected boolean ignoreNBT; protected String researchId; protected int eut; public T researchStack(@NotNull ItemStack researchStack) { if (!researchStack.isEmpty()) { this.researchStack = researchStack; + this.ignoreNBT = true; + } + return (T) this; + } + + public T researchStack(@NotNull ItemStack researchStack, boolean ignoreNBT) { + if (!researchStack.isEmpty()) { + this.researchStack = researchStack; + this.ignoreNBT = ignoreNBT; } return (T) this; } @@ -99,7 +109,8 @@ protected AssemblyLineRecipeBuilder.ResearchRecipeEntry build() { validateResearchItem(); if (duration <= 0) duration = DEFAULT_SCANNER_DURATION; if (eut <= 0) eut = DEFAULT_SCANNER_EUT; - return new AssemblyLineRecipeBuilder.ResearchRecipeEntry(researchId, researchStack, dataStack, duration, + return new AssemblyLineRecipeBuilder.ResearchRecipeEntry(researchId, researchStack, dataStack, ignoreNBT, + duration, eut, 0); } } @@ -148,7 +159,8 @@ protected AssemblyLineRecipeBuilder.ResearchRecipeEntry build() { int duration = totalCWU; if (eut <= 0) eut = DEFAULT_STATION_EUT; - return new AssemblyLineRecipeBuilder.ResearchRecipeEntry(researchId, researchStack, dataStack, duration, + return new AssemblyLineRecipeBuilder.ResearchRecipeEntry(researchId, researchStack, dataStack, ignoreNBT, + duration, eut, cwut); } } diff --git a/src/main/java/gregtech/api/recipes/machines/RecipeMapAssemblyLine.java b/src/main/java/gregtech/api/recipes/machines/RecipeMapAssemblyLine.java index 28cd542d11d..7edeb9a7f62 100644 --- a/src/main/java/gregtech/api/recipes/machines/RecipeMapAssemblyLine.java +++ b/src/main/java/gregtech/api/recipes/machines/RecipeMapAssemblyLine.java @@ -63,6 +63,10 @@ public boolean removeRecipe(@NotNull Recipe recipe) { @Override public void addDataStickEntry(@NotNull String researchId, @NotNull Recipe recipe) { + if (researchId.contains("xmetaitem.")) { + // save compatibility with an issue in 2.8.6, causing research IDs to change + addDataStickEntry(researchId.replace("xmetaitem.", "xitem.meta_item."), recipe); + } Collection collection = researchEntries.computeIfAbsent(researchId, (k) -> new ObjectOpenHashSet<>()); collection.add(recipe); } diff --git a/src/main/java/gregtech/api/recipes/recipeproperties/CleanroomProperty.java b/src/main/java/gregtech/api/recipes/recipeproperties/CleanroomProperty.java index ccbe97bdc17..2152e3a3e43 100644 --- a/src/main/java/gregtech/api/recipes/recipeproperties/CleanroomProperty.java +++ b/src/main/java/gregtech/api/recipes/recipeproperties/CleanroomProperty.java @@ -32,6 +32,13 @@ public void drawInfo(@NotNull Minecraft minecraft, int x, int y, int color, Obje minecraft.fontRenderer.drawString(I18n.format("gregtech.recipe.cleanroom", getName(type)), x, y, color); } + @Override + public int getInfoHeight(Object value) { + CleanroomType type = castValue(value); + if (type == null) return 0; + return super.getInfoHeight(value); + } + @NotNull private static String getName(@NotNull CleanroomType value) { String name = I18n.format(value.getTranslationKey()); diff --git a/src/main/java/gregtech/api/recipes/recipeproperties/DimensionProperty.java b/src/main/java/gregtech/api/recipes/recipeproperties/DimensionProperty.java new file mode 100644 index 00000000000..f233e7cd7f3 --- /dev/null +++ b/src/main/java/gregtech/api/recipes/recipeproperties/DimensionProperty.java @@ -0,0 +1,87 @@ +package gregtech.api.recipes.recipeproperties; + +import gregtech.api.worldgen.config.WorldGenRegistry; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.I18n; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; + +public class DimensionProperty extends RecipeProperty { + + public static final String KEY = "dimension"; + + private static DimensionProperty INSTANCE; + + private DimensionProperty() { + super(KEY, DimensionPropertyList.class); + } + + public static DimensionProperty getInstance() { + if (INSTANCE == null) + INSTANCE = new DimensionProperty(); + return INSTANCE; + } + + @Override + public void drawInfo(Minecraft minecraft, int x, int y, int color, Object value) { + DimensionPropertyList list = castValue(value); + + if (list.whiteListDimensions.size() > 0) + minecraft.fontRenderer.drawString(I18n.format("gregtech.recipe.dimensions", + getDimensionsForRecipe(castValue(value).whiteListDimensions)), x, y, color); + if (list.blackListDimensions.size() > 0) + minecraft.fontRenderer.drawString(I18n.format("gregtech.recipe.dimensions_blocked", + getDimensionsForRecipe(castValue(value).blackListDimensions)), x, y, color); + } + + private static String getDimensionsForRecipe(IntList value) { + Int2ObjectMap dimNames = WorldGenRegistry.getNamedDimensions(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.size(); i++) { + builder.append(dimNames.getOrDefault(value.getInt(i), String.valueOf(value.getInt(i)))); + if (i != value.size() - 1) + builder.append(", "); + } + String str = builder.toString(); + + if (str.length() >= 13) { + str = str.substring(0, 10) + ".."; + } + return str; + } + + // It would've been better to have one list and swap between blacklist and whitelist, but that would've been + // a bit awkward to apply to the property in practice. + public static class DimensionPropertyList { + + public static DimensionPropertyList EMPTY_LIST = new DimensionPropertyList(); + + public IntList whiteListDimensions = new IntArrayList(); + public IntList blackListDimensions = new IntArrayList(); + + public void add(int key, boolean toBlacklist) { + if (toBlacklist) { + blackListDimensions.add(key); + whiteListDimensions.rem(key); + } else { + whiteListDimensions.add(key); + blackListDimensions.rem(key); + } + } + + public void merge(DimensionPropertyList list) { + this.whiteListDimensions.addAll(list.whiteListDimensions); + this.blackListDimensions.addAll(list.blackListDimensions); + } + + public boolean checkDimension(int dim) { + boolean valid = true; + if (this.blackListDimensions.size() > 0) valid = !this.blackListDimensions.contains(dim); + if (this.whiteListDimensions.size() > 0) valid = this.whiteListDimensions.contains(dim); + return valid; + } + } +} diff --git a/src/main/java/gregtech/api/recipes/recipeproperties/FusionEUToStartProperty.java b/src/main/java/gregtech/api/recipes/recipeproperties/FusionEUToStartProperty.java index 12fd90e7748..60188d8cd5f 100644 --- a/src/main/java/gregtech/api/recipes/recipeproperties/FusionEUToStartProperty.java +++ b/src/main/java/gregtech/api/recipes/recipeproperties/FusionEUToStartProperty.java @@ -6,6 +6,7 @@ import net.minecraft.client.resources.I18n; import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.tuple.Pair; import java.util.Map; import java.util.TreeMap; @@ -14,7 +15,7 @@ public class FusionEUToStartProperty extends RecipeProperty { public static final String KEY = "eu_to_start"; - private static final TreeMap registeredFusionTiers = new TreeMap<>(); + private static final TreeMap> registeredFusionTiers = new TreeMap<>(); private static FusionEUToStartProperty INSTANCE; @@ -33,23 +34,29 @@ public static FusionEUToStartProperty getInstance() { @Override public void drawInfo(Minecraft minecraft, int x, int y, int color, Object value) { minecraft.fontRenderer.drawString(I18n.format("gregtech.recipe.eu_to_start", - TextFormattingUtil.formatLongToCompactString(castValue(value))) + getFusionTier(castValue(value)), x, y, + TextFormattingUtil.formatLongToCompactString(castValue(value))) + getFusionTierName(castValue(value)), + x, y, color); } - private static String getFusionTier(Long eu) { - Map.Entry mapEntry = registeredFusionTiers.ceilingEntry(eu); + private static String getFusionTierName(Long eu) { + Map.Entry> mapEntry = registeredFusionTiers.ceilingEntry(eu); if (mapEntry == null) { throw new IllegalArgumentException("Value is above registered maximum EU values"); } - return String.format(" %s", mapEntry.getValue()); + return String.format(" %s", mapEntry.getValue().getRight()); + } + + public static int getFusionTier(Long eu) { + Map.Entry> mapEntry = registeredFusionTiers.ceilingEntry(eu); + return mapEntry == null ? 0 : mapEntry.getValue().getLeft(); } public static void registerFusionTier(int tier, String shortName) { Validate.notNull(shortName); long maxEU = 16 * 10000000L * (long) Math.pow(2, tier - 6); - registeredFusionTiers.put(maxEU, shortName); + registeredFusionTiers.put(maxEU, Pair.of(tier, shortName)); } } diff --git a/src/main/java/gregtech/api/recipes/recipeproperties/GasCollectorDimensionProperty.java b/src/main/java/gregtech/api/recipes/recipeproperties/GasCollectorDimensionProperty.java deleted file mode 100644 index 94bb810e3b1..00000000000 --- a/src/main/java/gregtech/api/recipes/recipeproperties/GasCollectorDimensionProperty.java +++ /dev/null @@ -1,48 +0,0 @@ -package gregtech.api.recipes.recipeproperties; - -import gregtech.api.worldgen.config.WorldGenRegistry; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.I18n; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.IntList; - -public class GasCollectorDimensionProperty extends RecipeProperty { - - public static final String KEY = "dimension"; - - private static GasCollectorDimensionProperty INSTANCE; - - private GasCollectorDimensionProperty() { - super(KEY, IntList.class); - } - - public static GasCollectorDimensionProperty getInstance() { - if (INSTANCE == null) - INSTANCE = new GasCollectorDimensionProperty(); - return INSTANCE; - } - - @Override - public void drawInfo(Minecraft minecraft, int x, int y, int color, Object value) { - minecraft.fontRenderer.drawString(I18n.format("gregtech.recipe.dimensions", - getDimensionsForRecipe(castValue(value))), x, y, color); - } - - private static String getDimensionsForRecipe(IntList value) { - Int2ObjectMap dimNames = WorldGenRegistry.getNamedDimensions(); - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.size(); i++) { - builder.append(dimNames.getOrDefault(value.getInt(i), String.valueOf(value.getInt(i)))); - if (i != value.size() - 1) - builder.append(", "); - } - String str = builder.toString(); - - if (str.length() >= 13) { - str = str.substring(0, 10) + ".."; - } - return str; - } -} diff --git a/src/main/java/gregtech/api/recipes/recipeproperties/PrimitiveProperty.java b/src/main/java/gregtech/api/recipes/recipeproperties/PrimitiveProperty.java index 524fd7180fe..6419cd2aab0 100644 --- a/src/main/java/gregtech/api/recipes/recipeproperties/PrimitiveProperty.java +++ b/src/main/java/gregtech/api/recipes/recipeproperties/PrimitiveProperty.java @@ -24,6 +24,11 @@ public static PrimitiveProperty getInstance() { @Override public void drawInfo(Minecraft minecraft, int x, int y, int color, Object value) {} + @Override + public int getInfoHeight(Object value) { + return 0; + } + @Override public boolean hideTotalEU() { return true; diff --git a/src/main/java/gregtech/api/recipes/ui/impl/ResearchStationUI.java b/src/main/java/gregtech/api/recipes/ui/impl/ResearchStationUI.java index 917fc953dab..a0485997884 100644 --- a/src/main/java/gregtech/api/recipes/ui/impl/ResearchStationUI.java +++ b/src/main/java/gregtech/api/recipes/ui/impl/ResearchStationUI.java @@ -39,11 +39,11 @@ public ModularUI.Builder createJeiUITemplate(IItemHandlerModifiable importItems, GuiTextures.PROGRESS_BAR_RESEARCH_STATION_1, ProgressWidget.MoveType.HORIZONTAL)) .widget(new ProgressWidget(pairedSuppliers.getRight(), 119, 32, 10, 18, GuiTextures.PROGRESS_BAR_RESEARCH_STATION_2, ProgressWidget.MoveType.VERTICAL_DOWNWARDS)) - .widget(new SlotWidget(importItems, 0, 115, 50, true, true) + .widget(new SlotWidget(exportItems, 0, 115, 50, true, true) .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.DATA_ORB_OVERLAY)) .widget(new SlotWidget(importItems, 1, 43, 21, true, true) .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.SCANNER_OVERLAY)) - .widget(new SlotWidget(exportItems, 0, 97, 21, true, true) + .widget(new SlotWidget(importItems, 0, 97, 21, true, true) .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.RESEARCH_STATION_OVERLAY)); } } diff --git a/src/main/java/gregtech/api/terminal/TerminalRegistry.java b/src/main/java/gregtech/api/terminal/TerminalRegistry.java index 6da73912e23..e842eb0c7a9 100644 --- a/src/main/java/gregtech/api/terminal/TerminalRegistry.java +++ b/src/main/java/gregtech/api/terminal/TerminalRegistry.java @@ -5,6 +5,7 @@ import gregtech.api.terminal.hardware.Hardware; import gregtech.api.util.FileUtility; import gregtech.api.util.GTLog; +import gregtech.api.util.Mods; import gregtech.common.ConfigHolder; import gregtech.common.items.MetaItems; import gregtech.common.terminal.app.VirtualTankApp; @@ -131,7 +132,7 @@ public static void init() { .upgrade(1, MetaItems.EMITTER_HV.getStackForm(4), MetaItems.WORKSTATION_EV.getStackForm(2)) .defaultApp() .build(); - if (Loader.isModLoaded(GTValues.MODID_JEI)) { + if (Mods.JustEnoughItems.isModLoaded()) { AppRegistryBuilder.create(new RecipeChartApp()) .battery(GTValues.LV, 160) .upgrade(0, new ItemStack(Items.PAPER, 32)) diff --git a/src/main/java/gregtech/api/unification/material/materials/ElementMaterials.java b/src/main/java/gregtech/api/unification/material/materials/ElementMaterials.java index 37c745dc5c5..8d4af1f4fa7 100644 --- a/src/main/java/gregtech/api/unification/material/materials/ElementMaterials.java +++ b/src/main/java/gregtech/api/unification/material/materials/ElementMaterials.java @@ -42,6 +42,7 @@ public static void register() { Americium = new Material.Builder(3, gregtechId("americium")) .ingot(3) .liquid(new FluidBuilder().temperature(1449)) + .plasma() .color(0x287869).iconSet(METALLIC) .flags(EXT_METAL, GENERATE_FOIL, GENERATE_FINE_WIRE, GENERATE_DOUBLE_PLATE) .element(Elements.Am) @@ -814,6 +815,7 @@ public static void register() { Tin = new Material.Builder(112, gregtechId("tin")) .ingot(1) .liquid(new FluidBuilder().temperature(505)) + .plasma() .ore() .color(0xDCDCDC) .flags(EXT2_METAL, MORTAR_GRINDABLE, GENERATE_ROTOR, GENERATE_SPRING, GENERATE_SPRING_SMALL, diff --git a/src/main/java/gregtech/api/unification/material/materials/OrganicChemistryMaterials.java b/src/main/java/gregtech/api/unification/material/materials/OrganicChemistryMaterials.java index f47c7cc0a42..de2bdf4a703 100644 --- a/src/main/java/gregtech/api/unification/material/materials/OrganicChemistryMaterials.java +++ b/src/main/java/gregtech/api/unification/material/materials/OrganicChemistryMaterials.java @@ -194,7 +194,6 @@ public static void register() { Chloromethane = new Material.Builder(1024, gregtechId("chloromethane")) .gas() .color(0xC82CA0) - .flags(DISABLE_DECOMPOSITION) .components(Carbon, 1, Hydrogen, 3, Chlorine, 1) .build(); @@ -221,12 +220,14 @@ public static void register() { Propene = new Material.Builder(1028, gregtechId("propene")) .gas() .color(0xFFDD55) + .flags(DISABLE_DECOMPOSITION) .components(Carbon, 3, Hydrogen, 6) .build(); Ethane = new Material.Builder(1029, gregtechId("ethane")) .gas() .color(0xC8C8FF) + .flags(DISABLE_DECOMPOSITION) .components(Carbon, 2, Hydrogen, 6) .build(); @@ -268,7 +269,6 @@ public static void register() { Ethenone = new Material.Builder(1035, gregtechId("ethenone")) .fluid() .color(0x141446) - .flags(DISABLE_DECOMPOSITION) .components(Carbon, 2, Hydrogen, 2, Oxygen, 1) .build(); @@ -331,7 +331,6 @@ public static void register() { AceticAcid = new Material.Builder(1044, gregtechId("acetic_acid")) .liquid(new FluidBuilder().attribute(FluidAttributes.ACID)) .color(0xC8B4A0) - .flags(DISABLE_DECOMPOSITION) .components(Carbon, 2, Hydrogen, 4, Oxygen, 2) .build(); @@ -373,7 +372,6 @@ public static void register() { Acetone = new Material.Builder(1050, gregtechId("acetone")) .fluid() .color(0xAFAFAF) - .flags(DISABLE_DECOMPOSITION) .components(Carbon, 3, Hydrogen, 6, Oxygen, 1) .build(); diff --git a/src/main/java/gregtech/api/unification/material/properties/BlastProperty.java b/src/main/java/gregtech/api/unification/material/properties/BlastProperty.java index 172e0981ac7..61670ea18f9 100644 --- a/src/main/java/gregtech/api/unification/material/properties/BlastProperty.java +++ b/src/main/java/gregtech/api/unification/material/properties/BlastProperty.java @@ -1,17 +1,18 @@ package gregtech.api.unification.material.properties; +import gregtech.integration.groovy.GroovyScriptModule; + import crafttweaker.CraftTweakerAPI; import org.jetbrains.annotations.NotNull; public class BlastProperty implements IMaterialProperty { /** - * Blast Furnace Temperature of this Material. - * If below 1000K, Primitive Blast Furnace recipes will be also added. + * Blast Furnace Temperature of this Material. If below 1000K, Primitive Blast Furnace recipes will be also added. * If above 1750K, a Hot Ingot and its Vacuum Freezer recipe will be also added. *

- * If a Material with this Property has a Fluid, its temperature - * will be set to this if it is the default Fluid temperature. + * If a Material with this Property has a Fluid, its temperature will be set to this if it is the default Fluid + * temperature. */ private int blastTemperature; @@ -132,7 +133,10 @@ public void verifyProperty(MaterialProperties properties) { public static GasTier validateGasTier(String gasTierName) { if (gasTierName == null) return null; - else if ("LOW".equalsIgnoreCase(gasTierName)) return GasTier.LOW; + if (GroovyScriptModule.isCurrentlyRunning()) { + return GroovyScriptModule.parseAndValidateEnumValue(GasTier.class, gasTierName, "gas tier"); + } + if ("LOW".equalsIgnoreCase(gasTierName)) return GasTier.LOW; else if ("MID".equalsIgnoreCase(gasTierName)) return GasTier.MID; else if ("HIGH".equalsIgnoreCase(gasTierName)) return GasTier.HIGH; else if ("HIGHER".equalsIgnoreCase(gasTierName)) return GasTier.HIGHER; diff --git a/src/main/java/gregtech/api/unification/material/properties/FluidProperty.java b/src/main/java/gregtech/api/unification/material/properties/FluidProperty.java index 48f92084252..80c971fb4f1 100644 --- a/src/main/java/gregtech/api/unification/material/properties/FluidProperty.java +++ b/src/main/java/gregtech/api/unification/material/properties/FluidProperty.java @@ -2,6 +2,10 @@ import gregtech.api.fluids.store.FluidStorage; import gregtech.api.fluids.store.FluidStorageKey; +import gregtech.api.fluids.store.FluidStorageKeys; + +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -10,6 +14,7 @@ public class FluidProperty implements IMaterialProperty { private final FluidStorage storage = new FluidStorage(); private @Nullable FluidStorageKey primaryKey = null; + private @Nullable Fluid solidifyingFluid = null; public FluidProperty() {} @@ -33,4 +38,33 @@ public void setPrimaryKey(@Nullable FluidStorageKey primaryKey) { @Override public void verifyProperty(MaterialProperties properties) {} + + /** + * @return the Fluid which solidifies into the material. + */ + + public Fluid solidifiesFrom() { + if (this.solidifyingFluid == null) { + return getStorage().get(FluidStorageKeys.LIQUID); + } + return solidifyingFluid; + } + + /** + * @param amount the size of the returned FluidStack. + * @return a FluidStack of the Fluid which solidifies into the material. + */ + public FluidStack solidifiesFrom(int amount) { + return new FluidStack(solidifiesFrom(), amount); + } + + /** + * Sets the fluid that solidifies into the material. + * + * @param solidifyingFluid The Fluid which solidifies into the material. If left null, it will be left as the + * default value: the material's liquid. + */ + public void setSolidifyingFluid(@Nullable Fluid solidifyingFluid) { + this.solidifyingFluid = solidifyingFluid; + } } diff --git a/src/main/java/gregtech/api/unification/ore/OrePrefix.java b/src/main/java/gregtech/api/unification/ore/OrePrefix.java index 64eb11cd7fa..0a159028f1d 100644 --- a/src/main/java/gregtech/api/unification/ore/OrePrefix.java +++ b/src/main/java/gregtech/api/unification/ore/OrePrefix.java @@ -23,7 +23,15 @@ import stanhebben.zenscript.annotations.ZenClass; import stanhebben.zenscript.annotations.ZenMethod; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Predicate; @@ -467,6 +475,7 @@ public static void init() { block.modifyMaterialAmount(Materials.Glowstone, 4); block.modifyMaterialAmount(Materials.NetherQuartz, 4); + block.modifyMaterialAmount(Materials.CertusQuartz, 4); block.modifyMaterialAmount(Materials.Brick, 4); block.modifyMaterialAmount(Materials.Clay, 4); block.modifyMaterialAmount(Materials.Glass, 1); @@ -630,11 +639,11 @@ private void runGeneratedMaterialHandlers() { for (IOreRegistrationHandler registrationHandler : oreProcessingHandlers) { registrationHandler.processMaterial(this, registeredMaterial); } - currentMaterial.set(null); + currentMaterial.remove(); } // clear generated materials for next pass generatedMaterials.clear(); - currentProcessingPrefix.set(null); + currentProcessingPrefix.remove(); } public void setAlternativeOreName(String name) { diff --git a/src/main/java/gregtech/api/unification/ore/StoneType.java b/src/main/java/gregtech/api/unification/ore/StoneType.java index f92bb9c68bf..9e87a43a0b8 100644 --- a/src/main/java/gregtech/api/unification/ore/StoneType.java +++ b/src/main/java/gregtech/api/unification/ore/StoneType.java @@ -1,9 +1,9 @@ package gregtech.api.unification.ore; -import gregtech.api.GTValues; import gregtech.api.unification.material.Material; import gregtech.api.unification.material.properties.PropertyKey; import gregtech.api.util.GTControlledRegistry; +import gregtech.api.util.Mods; import gregtech.common.ConfigHolder; import gregtech.integration.jei.basic.OreByProduct; @@ -11,7 +11,6 @@ import net.minecraft.block.state.IBlockState; import net.minecraft.util.math.BlockPos; import net.minecraft.world.IBlockAccess; -import net.minecraftforge.fml.common.Loader; import com.google.common.base.Preconditions; import org.jetbrains.annotations.NotNull; @@ -50,7 +49,7 @@ public StoneType(int id, String name, SoundType soundType, OrePrefix processingP this.predicate = predicate::test; this.shouldBeDroppedAsItem = shouldBeDroppedAsItem || ConfigHolder.worldgen.allUniqueStoneTypes; STONE_TYPE_REGISTRY.register(id, name, this); - if (Loader.isModLoaded(GTValues.MODID_JEI) && this.shouldBeDroppedAsItem) { + if (Mods.JustEnoughItems.isModLoaded() && this.shouldBeDroppedAsItem) { OreByProduct.addOreByProductPrefix(this.processingPrefix); } } diff --git a/src/main/java/gregtech/api/util/AssemblyLineManager.java b/src/main/java/gregtech/api/util/AssemblyLineManager.java index f4f577996b2..a2652acfa84 100644 --- a/src/main/java/gregtech/api/util/AssemblyLineManager.java +++ b/src/main/java/gregtech/api/util/AssemblyLineManager.java @@ -121,33 +121,56 @@ public static void createDefaultResearchRecipe(@NotNull AssemblyLineRecipeBuilde for (AssemblyLineRecipeBuilder.ResearchRecipeEntry entry : builder.getRecipeEntries()) { createDefaultResearchRecipe(entry.getResearchId(), entry.getResearchStack(), entry.getDataStack(), + entry.getIgnoreNBT(), entry.getDuration(), entry.getEUt(), entry.getCWUt()); } } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") public static void createDefaultResearchRecipe(@NotNull String researchId, @NotNull ItemStack researchItem, @NotNull ItemStack dataItem, int duration, int EUt, int CWUt) { + createDefaultResearchRecipe(researchId, researchItem, dataItem, true, duration, EUt, CWUt); + } + + public static void createDefaultResearchRecipe(@NotNull String researchId, @NotNull ItemStack researchItem, + @NotNull ItemStack dataItem, boolean ignoreNBT, int duration, + int EUt, int CWUt) { if (!ConfigHolder.machines.enableResearch) return; NBTTagCompound compound = GTUtility.getOrCreateNbtCompound(dataItem); writeResearchToNBT(compound, researchId); if (CWUt > 0) { - RecipeMaps.RESEARCH_STATION_RECIPES.recipeBuilder() + RecipeBuilder researchBuilder = RecipeMaps.RESEARCH_STATION_RECIPES.recipeBuilder() .inputNBT(dataItem.getItem(), 1, dataItem.getMetadata(), NBTMatcher.ANY, NBTCondition.ANY) - .inputs(researchItem) .outputs(dataItem) .EUt(EUt) .CWUt(CWUt) - .totalCWU(duration) - .buildAndRegister(); + .totalCWU(duration); + + if (ignoreNBT) { + researchBuilder.inputNBT(researchItem.getItem(), 1, researchItem.getMetadata(), NBTMatcher.ANY, + NBTCondition.ANY); + } else { + researchBuilder.inputs(researchItem); + } + + researchBuilder.buildAndRegister(); } else { RecipeBuilder builder = RecipeMaps.SCANNER_RECIPES.recipeBuilder() .inputNBT(dataItem.getItem(), 1, dataItem.getMetadata(), NBTMatcher.ANY, NBTCondition.ANY) - .inputs(researchItem) .outputs(dataItem) .duration(duration) .EUt(EUt); + + if (ignoreNBT) { + builder.inputNBT(researchItem.getItem(), 1, researchItem.getMetadata(), NBTMatcher.ANY, + NBTCondition.ANY); + } else { + builder.inputs(researchItem); + } + builder.applyProperty(ScanProperty.getInstance(), true); builder.buildAndRegister(); } diff --git a/src/main/java/gregtech/api/util/CapesRegistry.java b/src/main/java/gregtech/api/util/CapesRegistry.java index 50972a56236..7d0acaa0ba6 100644 --- a/src/main/java/gregtech/api/util/CapesRegistry.java +++ b/src/main/java/gregtech/api/util/CapesRegistry.java @@ -1,6 +1,5 @@ package gregtech.api.util; -import gregtech.api.GTValues; import gregtech.api.GregTechAPI; import gregtech.client.renderer.texture.Textures; import gregtech.core.network.packets.PacketNotifyCapeChange; @@ -214,13 +213,13 @@ public static void addFreeCape(ResourceLocation cape) { private static final List> ctRegisterCapes = new ArrayList<>(); private static final List ctFreeCapes = new ArrayList<>(); - @Optional.Method(modid = GTValues.MODID_CT) + @Optional.Method(modid = Mods.Names.CRAFT_TWEAKER) @ZenMethod public static void registerCape(String advancement, String cape) { ctRegisterCapes.add(new Tuple<>(new ResourceLocation(advancement), new ResourceLocation(cape))); } - @Optional.Method(modid = GTValues.MODID_CT) + @Optional.Method(modid = Mods.Names.CRAFT_TWEAKER) @ZenMethod public static void registerFreeCape(String cape) { ctFreeCapes.add(new ResourceLocation(cape)); diff --git a/src/main/java/gregtech/api/util/ModCompatibility.java b/src/main/java/gregtech/api/util/ModCompatibility.java index 9881948147d..d6b627d3b6a 100644 --- a/src/main/java/gregtech/api/util/ModCompatibility.java +++ b/src/main/java/gregtech/api/util/ModCompatibility.java @@ -1,66 +1,22 @@ package gregtech.api.util; -import gregtech.api.util.world.DummyWorld; +import gregtech.client.utils.ItemRenderCompat; import net.minecraft.item.ItemStack; -import net.minecraft.util.ResourceLocation; -import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Objects; +import org.jetbrains.annotations.ApiStatus; @SideOnly(Side.CLIENT) public class ModCompatibility { - private static RefinedStorage refinedStorage; - - public static void initCompat() { - try { - Class itemClass = Class.forName("com.raoulvdberge.refinedstorage.item.ItemPattern"); - refinedStorage = new RefinedStorage(itemClass); - GTLog.logger.info("RefinedStorage found; enabling integration."); - } catch (ClassNotFoundException ignored) { - GTLog.logger.info("RefinedStorage not found; skipping integration."); - } catch (Throwable exception) { - GTLog.logger.error("Failed to enable RefinedStorage integration", exception); - } - } - + /** + * @deprecated Use {@link ItemRenderCompat#getRepresentedStack(ItemStack)} + */ + @ApiStatus.ScheduledForRemoval(inVersion = "2.10") + @Deprecated public static ItemStack getRealItemStack(ItemStack itemStack) { - if (refinedStorage != null && RefinedStorage.canHandleItemStack(itemStack)) { - return refinedStorage.getRealItemStack(itemStack); - } - return itemStack; - } - - private static class RefinedStorage { - - private final Method getPatternFromCacheMethod; - private final Method getOutputsMethod; - - public RefinedStorage(Class itemPatternClass) throws ReflectiveOperationException { - this.getPatternFromCacheMethod = itemPatternClass.getMethod("getPatternFromCache", World.class, - ItemStack.class); - this.getOutputsMethod = getPatternFromCacheMethod.getReturnType().getMethod("getOutputs"); - } - - public static boolean canHandleItemStack(ItemStack itemStack) { - ResourceLocation registryName = Objects.requireNonNull(itemStack.getItem().getRegistryName()); - return registryName.getNamespace().equals("refinedstorage") && - registryName.getPath().equals("pattern"); - } - - public ItemStack getRealItemStack(ItemStack itemStack) { - try { - Object craftingPattern = getPatternFromCacheMethod.invoke(null, DummyWorld.INSTANCE, itemStack); - List outputs = (List) getOutputsMethod.invoke(craftingPattern); - return outputs.isEmpty() ? itemStack : outputs.get(0); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to obtain item from ItemPattern", ex); - } - } + return ItemRenderCompat.getRepresentedStack(itemStack); } } diff --git a/src/main/java/gregtech/api/util/ModIncompatibilityException.java b/src/main/java/gregtech/api/util/ModIncompatibilityException.java new file mode 100644 index 00000000000..c21439cc5fe --- /dev/null +++ b/src/main/java/gregtech/api/util/ModIncompatibilityException.java @@ -0,0 +1,35 @@ +package gregtech.api.util; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiErrorScreen; +import net.minecraftforge.fml.client.CustomModLoadingErrorDisplayException; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.List; + +@SideOnly(Side.CLIENT) +public class ModIncompatibilityException extends CustomModLoadingErrorDisplayException { + + @SuppressWarnings("all") + private static final long serialVersionUID = 1L; + + private final List messages; + + public ModIncompatibilityException(List messages) { + this.messages = messages; + } + + @Override + public void initGui(GuiErrorScreen guiErrorScreen, FontRenderer fontRenderer) {} + + @Override + public void drawScreen(GuiErrorScreen errorScreen, FontRenderer fontRenderer, int mouseX, int mouseY, float time) { + int x = errorScreen.width / 2; + int y = 75; + for (String message : messages) { + errorScreen.drawCenteredString(fontRenderer, message, x, y, 0xFFFFFF); + y += 15; + } + } +} diff --git a/src/main/java/gregtech/api/util/Mods.java b/src/main/java/gregtech/api/util/Mods.java new file mode 100644 index 00000000000..c022672f095 --- /dev/null +++ b/src/main/java/gregtech/api/util/Mods.java @@ -0,0 +1,252 @@ +package gregtech.api.util; + +import gregtech.api.GTValues; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.common.registry.GameRegistry; +import net.minecraftforge.fml.relauncher.FMLLaunchHandler; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +public enum Mods { + + AdvancedRocketry(Names.ADVANCED_ROCKETRY), + AppliedEnergistics2(Names.APPLIED_ENERGISTICS2), + Baubles(Names.BAUBLES), + BinnieCore(Names.BINNIE_CORE), + BiomesOPlenty(Names.BIOMES_O_PLENTY), + BuildCraftCore(Names.BUILD_CRAFT_CORE), + Chisel(Names.CHISEL), + CoFHCore(Names.COFH_CORE), + CTM(Names.CONNECTED_TEXTURES_MOD), + CubicChunks(Names.CUBIC_CHUNKS), + CraftTweaker(Names.CRAFT_TWEAKER), + EnderCore(Names.ENDER_CORE), + EnderIO(Names.ENDER_IO), + ExtraBees(Names.EXTRA_BEES), + ExtraTrees(Names.EXTRA_TREES), + ExtraUtilities2(Names.EXTRA_UTILITIES2), + Forestry(Names.FORESTRY), + ForestryApiculture(Names.FORESTRY, forestryModule(Names.FORESTRY_APICULTURE)), + ForestryArboriculture(Names.FORESTRY, forestryModule(Names.FORESTRY_ARBORICULTURE)), + ForestryLepidopterology(Names.FORESTRY, forestryModule(Names.FORESTRY_LEPIDOPTEROLOGY)), + GalacticraftCore(Names.GALACTICRAFT_CORE), + Genetics(Names.GENETICS), + GregTech(Names.GREGTECH), + GregTechFoodOption(Names.GREGTECH_FOOD_OPTION), + GroovyScript(Names.GROOVY_SCRIPT), + GTCE2OC(Names.GTCE_2_OC), + HWYLA(Names.HWYLA), + ImmersiveEngineering(Names.IMMERSIVE_ENGINEERING), + IndustrialCraft2(Names.INDUSTRIAL_CRAFT2), + InventoryTweaks(Names.INVENTORY_TWEAKS), + JourneyMap(Names.JOURNEY_MAP), + JustEnoughItems(Names.JUST_ENOUGH_ITEMS), + MagicBees(Names.MAGIC_BEES), + Nothirium(Names.NOTHIRIUM), + NuclearCraft(Names.NUCLEAR_CRAFT, versionExcludes("2o")), + NuclearCraftOverhauled(Names.NUCLEAR_CRAFT, versionContains("2o")), + OpenComputers(Names.OPEN_COMPUTERS), + ProjectRedCore(Names.PROJECT_RED_CORE), + Railcraft(Names.RAILCRAFT), + RefinedStorage(Names.REFINED_STORAGE), + TechReborn(Names.TECH_REBORN), + TheOneProbe(Names.THE_ONE_PROBE), + TinkersConstruct(Names.TINKERS_CONSTRUCT), + TOPAddons(Names.TOP_ADDONS), + VoxelMap(Names.VOXEL_MAP), + XaerosMinimap(Names.XAEROS_MINIMAP), + + // Special Optifine handler, but consolidated here for simplicity + Optifine(null) { + + @Override + public boolean isModLoaded() { + if (this.modLoaded == null) { + try { + Class c = Class.forName("net.optifine.shaders.Shaders"); + Field f = c.getDeclaredField("shaderPackLoaded"); + f.setAccessible(true); + this.modLoaded = f.getBoolean(null); + } catch (Exception ignored) { + this.modLoaded = false; + } + } + return this.modLoaded; + } + }; + + public static class Names { + + public static final String ADVANCED_ROCKETRY = "advancedrocketry"; + public static final String APPLIED_ENERGISTICS2 = "appliedenergistics2"; + public static final String BAUBLES = "baubles"; + public static final String BINNIE_CORE = "binniecore"; + public static final String BIOMES_O_PLENTY = "biomesoplenty"; + public static final String BUILD_CRAFT_CORE = "buildcraftcore"; + public static final String CHISEL = "chisel"; + public static final String COFH_CORE = "cofhcore"; + public static final String CONNECTED_TEXTURES_MOD = "ctm"; + public static final String CUBIC_CHUNKS = "cubicchunks"; + public static final String CRAFT_TWEAKER = "crafttweaker"; + public static final String ENDER_CORE = "endercore"; + public static final String ENDER_IO = "enderio"; + public static final String EXTRA_BEES = "extrabees"; + public static final String EXTRA_TREES = "extratrees"; + public static final String EXTRA_UTILITIES2 = "extrautils2"; + public static final String FORESTRY = "forestry"; + public static final String FORESTRY_APICULTURE = "apiculture"; + public static final String FORESTRY_ARBORICULTURE = "arboriculture"; + public static final String FORESTRY_LEPIDOPTEROLOGY = "lepidopterology"; + public static final String GALACTICRAFT_CORE = "galacticraftcore"; + public static final String GENETICS = "genetics"; + public static final String GREGTECH = GTValues.MODID; + public static final String GREGTECH_FOOD_OPTION = "gregtechfoodoption"; + public static final String GROOVY_SCRIPT = "groovyscript"; + public static final String GTCE_2_OC = "gtce2oc"; + public static final String HWYLA = "hwyla"; + public static final String IMMERSIVE_ENGINEERING = "immersiveengineering"; + public static final String INDUSTRIAL_CRAFT2 = "ic2"; + public static final String INVENTORY_TWEAKS = "inventorytweaks"; + public static final String JOURNEY_MAP = "journeymap"; + public static final String JUST_ENOUGH_ITEMS = "jei"; + public static final String MAGIC_BEES = "magicbees"; + public static final String NOTHIRIUM = "nothirium"; + public static final String NUCLEAR_CRAFT = "nuclearcraft"; + public static final String OPEN_COMPUTERS = "opencomputers"; + public static final String PROJECT_RED_CORE = "projred-core"; + public static final String RAILCRAFT = "railcraft"; + public static final String REFINED_STORAGE = "refinedstorage"; + public static final String TECH_REBORN = "techreborn"; + public static final String THE_ONE_PROBE = "theoneprobe"; + public static final String TINKERS_CONSTRUCT = "tconstruct"; + public static final String TOP_ADDONS = "topaddons"; + public static final String VOXEL_MAP = "voxelmap"; + public static final String XAEROS_MINIMAP = "xaerominimap"; + } + + private final String ID; + private final Function extraCheck; + protected Boolean modLoaded; + + Mods(String ID) { + this.ID = ID; + this.extraCheck = null; + } + + /** + * @param extraCheck A supplier that can be used to test additional factors, such as + * checking if a mod is at a specific version, or a sub-mod is loaded. + * Used in cases like NC vs NCO, where the mod id is the same + * so the version has to be parsed to test which is loaded. + * Another case is checking for specific Forestry modules, checking + * if Forestry is loaded and if a specific module is enabled. + */ + Mods(String ID, Function extraCheck) { + this.ID = ID; + this.extraCheck = extraCheck; + } + + public boolean isModLoaded() { + if (this.modLoaded == null) { + this.modLoaded = Loader.isModLoaded(this.ID); + if (this.modLoaded) { + if (this.extraCheck != null && !this.extraCheck.apply(this)) { + this.modLoaded = false; + } + } + } + return this.modLoaded; + } + + /** + * Throw an exception if this mod is found to be loaded. + * This must be called in or after + * {@link net.minecraftforge.fml.common.event.FMLPreInitializationEvent}! + */ + public void throwIncompatibilityIfLoaded(String... customMessages) { + if (isModLoaded()) { + String modName = TextFormatting.BOLD + ID + TextFormatting.RESET; + List messages = new ArrayList<>(); + messages.add(modName + " mod detected, this mod is incompatible with GregTech CE Unofficial."); + messages.addAll(Arrays.asList(customMessages)); + if (FMLLaunchHandler.side() == Side.SERVER) { + throw new RuntimeException(String.join(",", messages)); + } else { + throwClientIncompatibility(messages); + } + } + } + + @SideOnly(Side.CLIENT) + private static void throwClientIncompatibility(List messages) { + throw new ModIncompatibilityException(messages); + } + + public ItemStack getItem(@NotNull String name) { + return getItem(name, 0, 1, null); + } + + @NotNull + public ItemStack getItem(@NotNull String name, int meta) { + return getItem(name, meta, 1, null); + } + + @NotNull + public ItemStack getItem(@NotNull String name, int meta, int amount) { + return getItem(name, meta, amount, null); + } + + @NotNull + public ItemStack getItem(@NotNull String name, int meta, int amount, @Nullable String nbt) { + if (!isModLoaded()) { + return ItemStack.EMPTY; + } + return GameRegistry.makeItemStack(ID + ":" + name, meta, amount, nbt); + } + + // Helpers for the extra checker + + /** Test if the mod version string contains the passed value. */ + private static Function versionContains(String versionPart) { + return mod -> { + if (mod.ID == null) return false; + if (!mod.isModLoaded()) return false; + ModContainer container = Loader.instance().getIndexedModList().get(mod.ID); + if (container == null) return false; + return container.getVersion().contains(versionPart); + }; + } + + /** Test if the mod version string does not contain the passed value. */ + private static Function versionExcludes(String versionPart) { + return mod -> { + if (mod.ID == null) return false; + if (!mod.isModLoaded()) return false; + ModContainer container = Loader.instance().getIndexedModList().get(mod.ID); + if (container == null) return false; + return !container.getVersion().contains(versionPart); + }; + } + + /** Test if a specific Forestry module is enabled. */ + private static Function forestryModule(String moduleID) { + if (Forestry.isModLoaded()) { + return mod -> forestry.modules.ModuleHelper.isEnabled(moduleID); + } else { + return $ -> false; + } + } +} diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java index 1e4b2102761..c55db969ee4 100644 --- a/src/main/java/gregtech/api/util/RelativeDirection.java +++ b/src/main/java/gregtech/api/util/RelativeDirection.java @@ -163,4 +163,33 @@ public static EnumFacing simulateAxisRotation(EnumFacing newFrontFacing, EnumFac return upwardsFacing.getOpposite(); } } + + /** + * Offset a BlockPos relatively in any direction by any amount. Pass negative values to offset down, right or + * backwards. + */ + public static BlockPos offsetPos(BlockPos pos, EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped, + int upOffset, int leftOffset, int forwardOffset) { + if (upOffset == 0 && leftOffset == 0 && forwardOffset == 0) { + return pos; + } + + int oX = 0, oY = 0, oZ = 0; + final EnumFacing relUp = UP.getRelativeFacing(frontFacing, upwardsFacing, isFlipped); + oX += relUp.getXOffset() * upOffset; + oY += relUp.getYOffset() * upOffset; + oZ += relUp.getZOffset() * upOffset; + + final EnumFacing relLeft = LEFT.getRelativeFacing(frontFacing, upwardsFacing, isFlipped); + oX += relLeft.getXOffset() * leftOffset; + oY += relLeft.getYOffset() * leftOffset; + oZ += relLeft.getZOffset() * leftOffset; + + final EnumFacing relForward = FRONT.getRelativeFacing(frontFacing, upwardsFacing, isFlipped); + oX += relForward.getXOffset() * forwardOffset; + oY += relForward.getYOffset() * forwardOffset; + oZ += relForward.getZOffset() * forwardOffset; + + return pos.add(oX, oY, oZ); + } } diff --git a/src/main/java/gregtech/api/util/oreglob/OreGlob.java b/src/main/java/gregtech/api/util/oreglob/OreGlob.java index 072e4959eeb..a560fcae1bf 100644 --- a/src/main/java/gregtech/api/util/oreglob/OreGlob.java +++ b/src/main/java/gregtech/api/util/oreglob/OreGlob.java @@ -7,21 +7,19 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.List; -import java.util.Set; -import java.util.function.Function; /** * Glob-like string matcher language designed for ore dictionary matching. *

- * An OreGlob instance provides two functions: the ability to match strings, - * and the ability to translate expression structure into user-friendly text - * explanations. The text can be either a plaintext, or a text formatted by standard + * An OreGlob instance provides two functions: the ability to match strings, and the ability to translate expression + * structure into user-friendly text explanations. The text can be either a plaintext, or a text formatted by standard * Minecraft text format. */ public abstract class OreGlob { - private static Function compiler; + private static OreGlobCompiler compiler; /** * Tries to compile the string expression into OreGlob instance. @@ -29,15 +27,31 @@ public abstract class OreGlob { * @param expression OreGlob expression * @return Compilation result * @throws IllegalStateException If compiler is not provided yet + * @deprecated use {@link #compile(String, boolean)} */ @NotNull + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") public static OreGlobCompileResult compile(@NotNull String expression) { + return compile(expression, true); + } + + /** + * Tries to compile the string expression into OreGlob instance. + * + * @param expression OreGlob expression + * @param ignoreCase Whether the resulting OreGlob instance should do case-insensitive matches + * @return Compilation result + * @throws IllegalStateException If compiler is not provided yet + */ + @NotNull + public static OreGlobCompileResult compile(@NotNull String expression, boolean ignoreCase) { if (compiler == null) throw new IllegalStateException("Compiler unavailable"); - return compiler.apply(expression); + return compiler.compile(expression, ignoreCase); } @ApiStatus.Internal - public static void setCompiler(@NotNull Function compiler) { + public static void setCompiler(@NotNull OreGlobCompiler compiler) { OreGlob.compiler = compiler; } @@ -60,30 +74,97 @@ public static void setCompiler(@NotNull Function c public abstract boolean matches(@NotNull String input); /** - * Tries to match each ore dictionary entries associated with given item. - * If any of them matches, {@code true} is returned. *

- * For items not associated with any ore dictionary entries, this method returns - * {@code true} if this instance matches empty string instead. + * Tries to match each ore dictionary entries associated with given item. If any of them matches, {@code true} is + * returned. + *

+ *

+ * For items not associated with any ore dictionary entries, this method returns {@code true} if this instance + * matches empty string instead. + *

* * @param stack Item input * @return Whether this instance matches the input + * @deprecated use {@link #matchesAll(ItemStack)} or {@link #matchesAny(ItemStack)} */ + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") public final boolean matches(@NotNull ItemStack stack) { - Set oreDicts = OreDictUnifier.getOreDictionaryNames(stack); - if (oreDicts.isEmpty()) { - return matches(""); - } else { - for (String oreDict : oreDicts) { - if (matches(oreDict)) return true; - } - return false; - } + return matchesAny(stack); + } + + /** + *

+ * Tries to match each ore dictionary entries associated with given item. If any of them matches, {@code true} is + * returned. + *

+ *

+ * For items not associated with any ore dictionary entries, this method returns {@code true} if this instance + * matches empty string instead. + *

+ * + * @param stack Item input + * @return Whether this instance matches the input + */ + public final boolean matchesAny(@NotNull ItemStack stack) { + return matchesAny(OreDictUnifier.getOreDictionaryNames(stack), true); + } + + /** + *

+ * Tries to match each ore dictionary entries associated with given item. If all of them matches, {@code true} is + * returned. + *

+ *

+ * For items not associated with any ore dictionary entries, this method returns {@code true} if this instance + * matches empty string instead. + *

+ * + * @param stack Item input + * @return Whether this instance matches the input + */ + public final boolean matchesAll(@NotNull ItemStack stack) { + return matchesAll(OreDictUnifier.getOreDictionaryNames(stack), true); + } + + /** + *

+ * Tries to match each input. If any of them matches, {@code true} is returned. + *

+ * + * @param inputs Collection of input strings + * @param specialEmptyMatch If {@code true}, this method will match an empty string ({@code ""}) if the input + * collection is empty. If {@code true}, this method will return {@code false} in such + * scenario. + * @return Whether this instance matches the input + */ + public final boolean matchesAny(@NotNull Collection inputs, boolean specialEmptyMatch) { + if (specialEmptyMatch && inputs.isEmpty()) return matches(""); + for (String input : inputs) if (matches(input)) return true; + return false; + } + + /** + *

+ * Tries to match each input. If all of them matches, {@code true} is returned. Note that this method does not have + * special case for empty inputs. + *

+ * + * @param inputs Collection of input strings + * @param specialEmptyMatch If {@code true}, this method will match an empty string ({@code ""}) if the input + * collection is empty. If {@code true}, this method will return {@code true} in such + * scenario. + * @return Whether this instance matches the input + */ + public final boolean matchesAll(@NotNull Collection inputs, boolean specialEmptyMatch) { + if (specialEmptyMatch && inputs.isEmpty()) return matches(""); + for (String input : inputs) if (!matches(input)) return false; + return true; } /** - * Visualize this instance with standard Minecraft text formatting. Two spaces (' ') will - * be used as indentation. + * Visualize this instance with standard Minecraft text formatting. Two spaces ({@code ' '}) will be used as + * indentation. * * @return Formatted visualization * @see OreGlob#toFormattedString(String) diff --git a/src/main/java/gregtech/api/util/oreglob/OreGlobCompiler.java b/src/main/java/gregtech/api/util/oreglob/OreGlobCompiler.java new file mode 100644 index 00000000000..d66fb7785a0 --- /dev/null +++ b/src/main/java/gregtech/api/util/oreglob/OreGlobCompiler.java @@ -0,0 +1,10 @@ +package gregtech.api.util.oreglob; + +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface OreGlobCompiler { + + @NotNull + OreGlobCompileResult compile(@NotNull String expression, boolean ignoreCase); +} diff --git a/src/main/java/gregtech/api/worldgen/config/WorldGenRegistry.java b/src/main/java/gregtech/api/worldgen/config/WorldGenRegistry.java index 2a30f9e1eeb..858f28bc328 100644 --- a/src/main/java/gregtech/api/worldgen/config/WorldGenRegistry.java +++ b/src/main/java/gregtech/api/worldgen/config/WorldGenRegistry.java @@ -3,6 +3,7 @@ import gregtech.api.GTValues; import gregtech.api.util.FileUtility; import gregtech.api.util.GTLog; +import gregtech.api.util.Mods; import gregtech.api.worldgen.filler.BlacklistedBlockFiller; import gregtech.api.worldgen.filler.BlockFiller; import gregtech.api.worldgen.filler.LayeredBlockFiller; @@ -123,7 +124,7 @@ public void initializeRegistry() { } catch (IOException | RuntimeException exception) { GTLog.logger.fatal("Failed to initialize worldgen registry.", exception); } - if (Loader.isModLoaded("galacticraftcore")) { + if (Mods.GalacticraftCore.isModLoaded()) { try { Class transformerHooksClass = Class.forName("micdoodle8.mods.galacticraft.core.TransformerHooks"); Field otherModGeneratorsWhitelistField = transformerHooksClass diff --git a/src/main/java/gregtech/asm/GregTechTransformer.java b/src/main/java/gregtech/asm/GregTechTransformer.java index 4d98621cfa8..005ec295075 100644 --- a/src/main/java/gregtech/asm/GregTechTransformer.java +++ b/src/main/java/gregtech/asm/GregTechTransformer.java @@ -1,6 +1,6 @@ package gregtech.asm; -import gregtech.api.GTValues; +import gregtech.api.util.Mods; import gregtech.asm.util.ObfMapping; import gregtech.asm.util.TargetClassVisitor; import gregtech.asm.visitors.*; @@ -8,8 +8,6 @@ import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.launchwrapper.Launch; -import net.minecraftforge.fml.common.Loader; -import net.minecraftforge.fml.common.ModContainer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; @@ -136,13 +134,12 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) ClassReader classReader = new ClassReader(basicClass); ClassWriter classWriter = new ClassWriter(0); - // fix NC recipe compat different depending on overhaul vs underhaul - ModContainer container = Loader.instance().getIndexedModList().get(GTValues.MODID_NC); - if (container.getVersion().contains("2o")) { // overhauled + // fix NC recipe compat different depending on overhaul vs normal + if (Mods.NuclearCraftOverhauled.isModLoaded()) { classReader.accept(new TargetClassVisitor(classWriter, NuclearCraftRecipeHelperVisitor.TARGET_METHOD_NCO, NuclearCraftRecipeHelperVisitor::new), 0); - } else { + } else if (Mods.NuclearCraft.isModLoaded()) { classReader.accept(new TargetClassVisitor(classWriter, NuclearCraftRecipeHelperVisitor.TARGET_METHOD_NC, NuclearCraftRecipeHelperVisitor::new), 0); } diff --git a/src/main/java/gregtech/asm/hooks/CTMHooks.java b/src/main/java/gregtech/asm/hooks/CTMHooks.java index 33ddc0d7754..bf254934d5c 100644 --- a/src/main/java/gregtech/asm/hooks/CTMHooks.java +++ b/src/main/java/gregtech/asm/hooks/CTMHooks.java @@ -1,6 +1,6 @@ package gregtech.asm.hooks; -import gregtech.client.shader.Shaders; +import gregtech.api.util.Mods; import gregtech.client.utils.BloomEffectUtil; import net.minecraft.block.state.IBlockState; @@ -20,7 +20,7 @@ public class CTMHooks { public static ThreadLocal ENABLE = new ThreadLocal<>(); public static boolean checkLayerWithOptiFine(boolean canRenderInLayer, byte layers, BlockRenderLayer layer) { - if (Shaders.isOptiFineShaderPackLoaded()) { + if (Mods.Optifine.isModLoaded()) { if (canRenderInLayer) { if (layer == BloomEffectUtil.getBloomLayer()) return false; } else if ((layers >> BloomEffectUtil.getBloomLayer().ordinal() & 1) == 1 && @@ -34,7 +34,7 @@ public static boolean checkLayerWithOptiFine(boolean canRenderInLayer, byte laye public static List getQuadsWithOptiFine(List ret, BlockRenderLayer layer, IBakedModel bakedModel, IBlockState state, EnumFacing side, long rand) { - if (Shaders.isOptiFineShaderPackLoaded() && CTMHooks.ENABLE.get() == null) { + if (Mods.Optifine.isModLoaded() && CTMHooks.ENABLE.get() == null) { if (layer == BloomEffectUtil.getBloomLayer()) { return Collections.emptyList(); } else if (layer == BloomEffectUtil.getEffectiveBloomLayer()) { @@ -43,7 +43,7 @@ public static List getQuadsWithOptiFine(List ret, BlockRen ForgeHooksClient.setRenderLayer(BloomEffectUtil.getBloomLayer()); result.addAll(bakedModel.getQuads(state, side, rand)); ForgeHooksClient.setRenderLayer(layer); - CTMHooks.ENABLE.set(null); + CTMHooks.ENABLE.remove(); return result; } } diff --git a/src/main/java/gregtech/asm/visitors/RenderItemVisitor.java b/src/main/java/gregtech/asm/visitors/RenderItemVisitor.java index f1b87a1fc12..750de53af3b 100644 --- a/src/main/java/gregtech/asm/visitors/RenderItemVisitor.java +++ b/src/main/java/gregtech/asm/visitors/RenderItemVisitor.java @@ -1,6 +1,6 @@ package gregtech.asm.visitors; -import gregtech.api.GTValues; +import gregtech.api.util.Mods; import gregtech.asm.util.ObfMapping; import net.minecraftforge.fml.common.Loader; @@ -28,7 +28,7 @@ public static void transform(Iterator methods) { callRenderLampOverlay.add(new MethodInsnNode(INVOKESTATIC, "gregtech/asm/hooks/RenderItemHooks", "renderLampOverlay", "(Lnet/minecraft/item/ItemStack;II)V", false)); - boolean enderCoreLoaded = Loader.instance().getIndexedModList().containsKey(GTValues.MODID_ECORE); + boolean enderCoreLoaded = Loader.instance().getIndexedModList().containsKey(Mods.Names.ENDER_CORE); // do not conflict with EnderCore's changes, which already do what we need InsnList callRenderElectricBar; diff --git a/src/main/java/gregtech/client/ClientProxy.java b/src/main/java/gregtech/client/ClientProxy.java index 0210563b18c..7fb4773d478 100644 --- a/src/main/java/gregtech/client/ClientProxy.java +++ b/src/main/java/gregtech/client/ClientProxy.java @@ -10,12 +10,18 @@ import gregtech.api.unification.stack.UnificationEntry; import gregtech.api.util.FluidTooltipUtil; import gregtech.api.util.IBlockOre; -import gregtech.api.util.ModCompatibility; +import gregtech.api.util.Mods; import gregtech.client.model.customtexture.CustomTextureModelHandler; import gregtech.client.model.customtexture.MetadataSectionCTM; import gregtech.client.renderer.handler.FacadeRenderer; import gregtech.client.renderer.handler.MetaTileEntityRenderer; -import gregtech.client.renderer.pipe.*; +import gregtech.client.renderer.pipe.CableRenderer; +import gregtech.client.renderer.pipe.FluidPipeRenderer; +import gregtech.client.renderer.pipe.ItemPipeRenderer; +import gregtech.client.renderer.pipe.LaserPipeRenderer; +import gregtech.client.renderer.pipe.OpticalPipeRenderer; +import gregtech.client.renderer.pipe.PipeRenderer; +import gregtech.client.utils.ItemRenderCompat; import gregtech.client.utils.TooltipHelper; import gregtech.common.CommonProxy; import gregtech.common.ConfigHolder; @@ -47,7 +53,6 @@ import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.IFluidBlock; import net.minecraftforge.fluids.capability.templates.FluidHandlerItemStack; -import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -72,7 +77,7 @@ public void onPreLoad() { SoundSystemConfig.setNumberNormalChannels(ConfigHolder.client.maxNumSounds); - if (!Loader.isModLoaded(GTValues.MODID_CTM)) { + if (!Mods.CTM.isModLoaded()) { Minecraft.getMinecraft().metadataSerializer.registerMetadataSectionType(new MetadataSectionCTM.Serializer(), MetadataSectionCTM.class); MinecraftForge.EVENT_BUS.register(CustomTextureModelHandler.INSTANCE); @@ -101,7 +106,7 @@ public void onLoad() { public void onPostLoad() { super.onPostLoad(); TerminalRegistry.initTerminalFiles(); - ModCompatibility.initCompat(); + ItemRenderCompat.init(); FacadeRenderer.init(); } diff --git a/src/main/java/gregtech/client/model/pipeline/VertexLighterFlatSpecial.java b/src/main/java/gregtech/client/model/pipeline/VertexLighterFlatSpecial.java index 75f05440797..9f8580344ec 100644 --- a/src/main/java/gregtech/client/model/pipeline/VertexLighterFlatSpecial.java +++ b/src/main/java/gregtech/client/model/pipeline/VertexLighterFlatSpecial.java @@ -1,6 +1,6 @@ package gregtech.client.model.pipeline; -import gregtech.client.shader.Shaders; +import gregtech.api.util.Mods; import net.minecraft.client.renderer.color.BlockColors; import net.minecraft.client.renderer.vertex.VertexFormat; @@ -134,7 +134,7 @@ protected void processQuad() { updateColor(normal[v], color[v], x, y, z, tint, multiplier); // When enabled this causes the rendering to be black with Optifine - if (!Shaders.isOptiFineShaderPackLoaded() && diffuse) { + if (!Mods.Optifine.isModLoaded() && diffuse) { float d = LightUtil.diffuseLight(normal[v][0], normal[v][1], normal[v][2]); for (int i = 0; i < 3; i++) { color[v][i] *= d; diff --git a/src/main/java/gregtech/client/model/pipeline/VertexLighterSmoothAoSpecial.java b/src/main/java/gregtech/client/model/pipeline/VertexLighterSmoothAoSpecial.java index 79b83e9998e..f1d6f852e3c 100644 --- a/src/main/java/gregtech/client/model/pipeline/VertexLighterSmoothAoSpecial.java +++ b/src/main/java/gregtech/client/model/pipeline/VertexLighterSmoothAoSpecial.java @@ -1,6 +1,6 @@ package gregtech.client.model.pipeline; -import gregtech.client.shader.Shaders; +import gregtech.api.util.Mods; import net.minecraft.client.renderer.color.BlockColors; import net.minecraft.util.math.MathHelper; @@ -15,7 +15,7 @@ public VertexLighterSmoothAoSpecial(BlockColors colors) { @Override protected void updateLightmap(float[] normal, float[] lightmap, float x, float y, float z) { - if (Shaders.isOptiFineShaderPackLoaded()) { + if (Mods.Optifine.isModLoaded()) { super.updateLightmap(normal, lightmap, x, y, z); return; } @@ -28,7 +28,7 @@ protected void updateLightmap(float[] normal, float[] lightmap, float x, float y protected void updateColor(float[] normal, float[] color, float x, float y, float z, float tint, int multiplier) { super.updateColor(normal, color, x, y, z, tint, multiplier); - if (Shaders.isOptiFineShaderPackLoaded()) { + if (Mods.Optifine.isModLoaded()) { return; } @@ -146,7 +146,7 @@ protected float getAo(float x, float y, float z) { @Override public void updateBlockInfo() { - if (Shaders.isOptiFineShaderPackLoaded()) { + if (Mods.Optifine.isModLoaded()) { super.updateBlockInfo(); return; } diff --git a/src/main/java/gregtech/client/particle/VanillaParticleEffects.java b/src/main/java/gregtech/client/particle/VanillaParticleEffects.java index 2765d3945d9..ddfa0592369 100644 --- a/src/main/java/gregtech/client/particle/VanillaParticleEffects.java +++ b/src/main/java/gregtech/client/particle/VanillaParticleEffects.java @@ -26,35 +26,6 @@ public enum VanillaParticleEffects implements IMachineParticleEffect { mte.getWorld().spawnParticle(EnumParticleTypes.SMOKE_NORMAL, x, y, z, 0, 0, 0); }), - MUFFLER_SMOKE(mte -> { - if (mte.getWorld() == null || mte.getPos() == null) return; - - BlockPos pos = mte.getPos(); - EnumFacing facing = mte.getFrontFacing(); - float xPos = facing.getXOffset() * 0.76F + pos.getX() + 0.25F; - float yPos = facing.getYOffset() * 0.76F + pos.getY() + 0.25F; - float zPos = facing.getZOffset() * 0.76F + pos.getZ() + 0.25F; - - float ySpd = facing.getYOffset() * 0.1F + 0.2F + 0.1F * GTValues.RNG.nextFloat(); - float xSpd; - float zSpd; - - if (facing.getYOffset() == -1) { - float temp = GTValues.RNG.nextFloat() * 2 * (float) Math.PI; - xSpd = (float) Math.sin(temp) * 0.1F; - zSpd = (float) Math.cos(temp) * 0.1F; - } else { - xSpd = facing.getXOffset() * (0.1F + 0.2F * GTValues.RNG.nextFloat()); - zSpd = facing.getZOffset() * (0.1F + 0.2F * GTValues.RNG.nextFloat()); - } - - xPos += GTValues.RNG.nextFloat() * 0.5F; - yPos += GTValues.RNG.nextFloat() * 0.5F; - zPos += GTValues.RNG.nextFloat() * 0.5F; - - mte.getWorld().spawnParticle(EnumParticleTypes.SMOKE_LARGE, xPos, yPos, zPos, xSpd, ySpd, zSpd); - }), - PBF_SMOKE(mte -> { if (mte.getWorld() == null || mte.getPos() == null) return; @@ -167,12 +138,12 @@ public void runEffect(@NotNull MetaTileEntity metaTileEntity) { } @SideOnly(Side.CLIENT) - public static void defaultFrontEffect(MetaTileEntity mte, EnumParticleTypes... particles) { + public static void defaultFrontEffect(@NotNull MetaTileEntity mte, EnumParticleTypes... particles) { defaultFrontEffect(mte, 0.0F, particles); } @SideOnly(Side.CLIENT) - public static void defaultFrontEffect(MetaTileEntity mte, float yOffset, EnumParticleTypes... particles) { + public static void defaultFrontEffect(@NotNull MetaTileEntity mte, float yOffset, EnumParticleTypes... particles) { if (particles == null || particles.length == 0) return; if (mte.getWorld() == null || mte.getPos() == null) return; @@ -201,4 +172,34 @@ public static void defaultFrontEffect(MetaTileEntity mte, float yOffset, EnumPar mte.getWorld().spawnParticle(particle, x, y, z, 0, 0, 0); } } + + @SideOnly(Side.CLIENT) + public static void mufflerEffect(@NotNull MetaTileEntity mte, @NotNull EnumParticleTypes particle) { + if (mte.getWorld() == null || mte.getPos() == null) return; + + BlockPos pos = mte.getPos(); + EnumFacing facing = mte.getFrontFacing(); + float xPos = facing.getXOffset() * 0.76F + pos.getX() + 0.25F; + float yPos = facing.getYOffset() * 0.76F + pos.getY() + 0.25F; + float zPos = facing.getZOffset() * 0.76F + pos.getZ() + 0.25F; + + float ySpd = facing.getYOffset() * 0.1F + 0.2F + 0.1F * GTValues.RNG.nextFloat(); + float xSpd; + float zSpd; + + if (facing.getYOffset() == -1) { + float temp = GTValues.RNG.nextFloat() * 2 * (float) Math.PI; + xSpd = (float) Math.sin(temp) * 0.1F; + zSpd = (float) Math.cos(temp) * 0.1F; + } else { + xSpd = facing.getXOffset() * (0.1F + 0.2F * GTValues.RNG.nextFloat()); + zSpd = facing.getZOffset() * (0.1F + 0.2F * GTValues.RNG.nextFloat()); + } + + xPos += GTValues.RNG.nextFloat() * 0.5F; + yPos += GTValues.RNG.nextFloat() * 0.5F; + zPos += GTValues.RNG.nextFloat() * 0.5F; + + mte.getWorld().spawnParticle(particle, xPos, yPos, zPos, xSpd, ySpd, zSpd); + } } diff --git a/src/main/java/gregtech/client/renderer/handler/CCLBlockRenderer.java b/src/main/java/gregtech/client/renderer/handler/CCLBlockRenderer.java index 5dabff41d6a..e2046685f86 100644 --- a/src/main/java/gregtech/client/renderer/handler/CCLBlockRenderer.java +++ b/src/main/java/gregtech/client/renderer/handler/CCLBlockRenderer.java @@ -2,9 +2,9 @@ import gregtech.api.util.GTLog; import gregtech.api.util.GTUtility; -import gregtech.api.util.ModCompatibility; import gregtech.client.renderer.ICCLBlockRenderer; import gregtech.client.renderer.texture.Textures; +import gregtech.client.utils.ItemRenderCompat; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; @@ -60,10 +60,10 @@ public void onModelsBake(ModelBakeEvent event) { @Override public void renderItem(ItemStack rawStack, ItemCameraTransforms.TransformType transformType) { - ItemStack stack = ModCompatibility.getRealItemStack(rawStack); - if (stack.getItem() instanceof ItemBlock && - ((ItemBlock) stack.getItem()).getBlock() instanceof ICCLBlockRenderer) { - ((ICCLBlockRenderer) ((ItemBlock) stack.getItem()).getBlock()).renderItem(stack, transformType); + ItemStack stack = ItemRenderCompat.getRepresentedStack(rawStack); + if (stack.getItem() instanceof ItemBlock itemBlock && + itemBlock.getBlock() instanceof ICCLBlockRenderer renderer) { + renderer.renderItem(stack, transformType); } } diff --git a/src/main/java/gregtech/client/renderer/handler/FacadeRenderer.java b/src/main/java/gregtech/client/renderer/handler/FacadeRenderer.java index fcd0c3ba523..6db1848659a 100644 --- a/src/main/java/gregtech/client/renderer/handler/FacadeRenderer.java +++ b/src/main/java/gregtech/client/renderer/handler/FacadeRenderer.java @@ -2,11 +2,11 @@ import gregtech.api.cover.CoverUtil; import gregtech.api.items.metaitem.MetaItem; -import gregtech.api.util.ModCompatibility; import gregtech.client.model.pipeline.VertexLighterFlatSpecial; import gregtech.client.model.pipeline.VertexLighterSmoothAoSpecial; import gregtech.client.utils.AdvCCRSConsumer; import gregtech.client.utils.FacadeBlockAccess; +import gregtech.client.utils.ItemRenderCompat; import gregtech.common.covers.facade.FacadeHelper; import gregtech.common.items.behaviors.FacadeItem; @@ -82,7 +82,7 @@ public static void init() { @Override public void renderItem(ItemStack rawStack, ItemCameraTransforms.TransformType transformType) { - ItemStack itemStack = ModCompatibility.getRealItemStack(rawStack); + ItemStack itemStack = ItemRenderCompat.getRepresentedStack(rawStack); if (!(itemStack.getItem() instanceof MetaItem)) { return; } diff --git a/src/main/java/gregtech/client/renderer/handler/GTExplosiveRenderer.java b/src/main/java/gregtech/client/renderer/handler/GTExplosiveRenderer.java new file mode 100644 index 00000000000..4b0efe1ae63 --- /dev/null +++ b/src/main/java/gregtech/client/renderer/handler/GTExplosiveRenderer.java @@ -0,0 +1,81 @@ +package gregtech.client.renderer.handler; + +import gregtech.common.entities.EntityGTExplosive; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BlockRendererDispatcher; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.MathHelper; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import org.jetbrains.annotations.NotNull; + +@SideOnly(Side.CLIENT) +public class GTExplosiveRenderer extends Render { + + public GTExplosiveRenderer(RenderManager renderManager) { + super(renderManager); + this.shadowSize = 0.5F; + } + + @Override + public void doRender(@NotNull T entity, double x, double y, double z, float entityYaw, float partialTicks) { + BlockRendererDispatcher brd = Minecraft.getMinecraft().getBlockRendererDispatcher(); + GlStateManager.pushMatrix(); + GlStateManager.translate((float) x, (float) y + 0.5F, (float) z); + float f2; + if ((float) entity.getFuse() - partialTicks + 1.0F < 10.0F) { + f2 = 1.0F - ((float) entity.getFuse() - partialTicks + 1.0F) / 10.0F; + f2 = MathHelper.clamp(f2, 0.0F, 1.0F); + f2 *= f2; + f2 *= f2; + float f1 = 1.0F + f2 * 0.3F; + GlStateManager.scale(f1, f1, f1); + } + + f2 = (1.0F - ((float) entity.getFuse() - partialTicks + 1.0F) / 100.0F) * 0.8F; + this.bindEntityTexture(entity); + GlStateManager.rotate(-90.0F, 0.0F, 1.0F, 0.0F); + GlStateManager.translate(-0.5F, -0.5F, 0.5F); + + IBlockState state = entity.getExplosiveState(); + brd.renderBlockBrightness(state, entity.getBrightness()); + GlStateManager.translate(0.0F, 0.0F, 1.0F); + if (this.renderOutlines) { + GlStateManager.enableColorMaterial(); + GlStateManager.enableOutlineMode(this.getTeamColor(entity)); + brd.renderBlockBrightness(state, 1.0F); + GlStateManager.disableOutlineMode(); + GlStateManager.disableColorMaterial(); + } else if (entity.getFuse() / 5 % 2 == 0) { + GlStateManager.disableTexture2D(); + GlStateManager.disableLighting(); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.DST_ALPHA); + GlStateManager.color(1.0F, 1.0F, 1.0F, f2); + GlStateManager.doPolygonOffset(-3.0F, -3.0F); + GlStateManager.enablePolygonOffset(); + brd.renderBlockBrightness(state, 1.0F); + GlStateManager.doPolygonOffset(0.0F, 0.0F); + GlStateManager.disablePolygonOffset(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.disableBlend(); + GlStateManager.enableLighting(); + GlStateManager.enableTexture2D(); + } + + GlStateManager.popMatrix(); + super.doRender(entity, x, y, z, entityYaw, partialTicks); + } + + @Override + protected ResourceLocation getEntityTexture(@NotNull T entity) { + return TextureMap.LOCATION_BLOCKS_TEXTURE; + } +} diff --git a/src/main/java/gregtech/client/renderer/handler/MetaTileEntityRenderer.java b/src/main/java/gregtech/client/renderer/handler/MetaTileEntityRenderer.java index 5aa513fea8c..8e0fe1f6962 100644 --- a/src/main/java/gregtech/client/renderer/handler/MetaTileEntityRenderer.java +++ b/src/main/java/gregtech/client/renderer/handler/MetaTileEntityRenderer.java @@ -4,9 +4,9 @@ import gregtech.api.metatileentity.MetaTileEntity; import gregtech.api.util.GTLog; import gregtech.api.util.GTUtility; -import gregtech.api.util.ModCompatibility; import gregtech.client.renderer.CubeRendererState; import gregtech.client.renderer.texture.Textures; +import gregtech.client.utils.ItemRenderCompat; import net.minecraft.block.state.IBlockState; import net.minecraft.client.renderer.BufferBuilder; @@ -72,7 +72,7 @@ public void onModelsBake(ModelBakeEvent event) { @Override public void renderItem(ItemStack rawStack, TransformType transformType) { - ItemStack stack = ModCompatibility.getRealItemStack(rawStack); + ItemStack stack = ItemRenderCompat.getRepresentedStack(rawStack); MetaTileEntity metaTileEntity = GTUtility.getMetaTileEntity(stack); if (metaTileEntity == null) { return; @@ -94,7 +94,7 @@ public void renderItem(ItemStack rawStack, TransformType transformType) { @Override public boolean renderBlock(IBlockAccess world, BlockPos pos, IBlockState state, BufferBuilder buffer) { MetaTileEntity metaTileEntity = GTUtility.getMetaTileEntity(world, pos); - if (metaTileEntity == null) { + if (metaTileEntity == null || !metaTileEntity.isValid()) { return false; } CCRenderState renderState = CCRenderState.instance(); @@ -115,7 +115,7 @@ public boolean renderBlock(IBlockAccess world, BlockPos pos, IBlockState state, metaTileEntity.renderCovers(renderState, translation.copy(), renderLayer); - Textures.RENDER_STATE.set(null); + Textures.RENDER_STATE.remove(); return true; } diff --git a/src/main/java/gregtech/client/renderer/pipe/PipeRenderer.java b/src/main/java/gregtech/client/renderer/pipe/PipeRenderer.java index 841d230d0f5..e3195dba22c 100644 --- a/src/main/java/gregtech/client/renderer/pipe/PipeRenderer.java +++ b/src/main/java/gregtech/client/renderer/pipe/PipeRenderer.java @@ -11,9 +11,9 @@ import gregtech.api.unification.material.Material; import gregtech.api.unification.material.info.MaterialIconType; import gregtech.api.util.GTUtility; -import gregtech.api.util.ModCompatibility; import gregtech.client.renderer.CubeRendererState; import gregtech.client.renderer.texture.Textures; +import gregtech.client.utils.ItemRenderCompat; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; @@ -100,7 +100,7 @@ public static void initializeRestrictor(TextureMap map) { static { FACE_BORDER_MAP.put(EnumFacing.DOWN, - borderMap(EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.EAST, EnumFacing.WEST)); + borderMap(EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.WEST, EnumFacing.EAST)); FACE_BORDER_MAP.put(EnumFacing.UP, borderMap(EnumFacing.NORTH, EnumFacing.SOUTH, EnumFacing.WEST, EnumFacing.EAST)); FACE_BORDER_MAP.put(EnumFacing.NORTH, @@ -150,7 +150,7 @@ public abstract void buildRenderer(PipeRenderContext renderContext, BlockPipe(); if (allowedShader()) { initShaders(); } - try { // hook optFine. thanks to Scannable. - final Class clazz = Class.forName("net.optifine.shaders.Shaders"); - final Field shaderPackLoaded = clazz.getDeclaredField("shaderPackLoaded"); - shaderPackLoaded.setAccessible(true); - isShaderPackLoaded = () -> { - try { - return shaderPackLoaded.getBoolean(null); - } catch (final IllegalAccessException e) { - GTLog.logger.warn( - "Failed reading field indicating whether shaders are enabled. Shader mod integration disabled."); - isShaderPackLoaded = null; - return false; - } - }; - GTLog.logger.info("Find optiFine mod loaded."); - } catch (ClassNotFoundException e) { - GTLog.logger.info("No optiFine mod found."); - } catch (NoSuchFieldException | NoClassDefFoundError e) { - GTLog.logger.warn("Failed integrating with shader mod. Ignoring."); - } } public static void initShaders() { @@ -128,8 +105,11 @@ public static boolean allowedShader() { return OpenGlHelper.shadersSupported && ConfigHolder.client.shader.useShader; } + /** @deprecated Use {@link Mods#Optifine} to check this instead. */ + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") public static boolean isOptiFineShaderPackLoaded() { - return isShaderPackLoaded != null && isShaderPackLoaded.getAsBoolean(); + return Mods.Optifine.isModLoaded(); } public static Framebuffer renderFullImageInFBO(Framebuffer fbo, ShaderObject frag, diff --git a/src/main/java/gregtech/client/utils/BloomEffectUtil.java b/src/main/java/gregtech/client/utils/BloomEffectUtil.java index d1ac6f07cc0..2dcc7d0be14 100644 --- a/src/main/java/gregtech/client/utils/BloomEffectUtil.java +++ b/src/main/java/gregtech/client/utils/BloomEffectUtil.java @@ -1,6 +1,7 @@ package gregtech.client.utils; import gregtech.api.metatileentity.MetaTileEntity; +import gregtech.api.util.Mods; import gregtech.client.particle.GTParticle; import gregtech.client.renderer.IRenderSetup; import gregtech.client.shader.Shaders; @@ -20,7 +21,6 @@ import net.minecraft.launchwrapper.Launch; import net.minecraft.util.BlockRenderLayer; import net.minecraftforge.common.util.EnumHelper; -import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -101,7 +101,7 @@ public static BlockRenderLayer getEffectiveBloomLayer() { */ @Contract("null -> _; !null -> !null") public static BlockRenderLayer getEffectiveBloomLayer(BlockRenderLayer fallback) { - return Shaders.isOptiFineShaderPackLoaded() ? fallback : bloom; + return Mods.Optifine.isModLoaded() ? fallback : bloom; } /** @@ -133,7 +133,7 @@ public static BlockRenderLayer getEffectiveBloomLayer(boolean isBloomActive) { */ @Contract("_, null -> _; _, !null -> !null") public static BlockRenderLayer getEffectiveBloomLayer(boolean isBloomActive, BlockRenderLayer fallback) { - return Shaders.isOptiFineShaderPackLoaded() || !isBloomActive ? fallback : bloom; + return Mods.Optifine.isModLoaded() || !isBloomActive ? fallback : bloom; } /** @@ -221,7 +221,7 @@ public static BloomRenderTicket registerBloomRender(@Nullable IRenderSetup setup @NotNull BloomType bloomType, @NotNull IBloomEffect render, @Nullable Predicate validityChecker) { - if (Shaders.isOptiFineShaderPackLoaded()) return null; + if (Mods.Optifine.isModLoaded()) return null; BloomRenderTicket ticket = new BloomRenderTicket(setup, bloomType, render, validityChecker); SCHEDULED_BLOOM_RENDERS.add(ticket); return ticket; @@ -253,7 +253,7 @@ public boolean test(BloomRenderTicket bloomRenderTicket) { public static void init() { bloom = EnumHelper.addEnum(BlockRenderLayer.class, "BLOOM", new Class[] { String.class }, "Bloom"); BLOOM = bloom; - if (Loader.isModLoaded("nothirium")) { + if (Mods.Nothirium.isModLoaded()) { try { // Nothirium hard copies the BlockRenderLayer enum into a ChunkRenderPass enum. Add our BLOOM layer to // that too. @@ -285,7 +285,7 @@ public static int renderBloomBlockLayer(RenderGlobal renderGlobal, Minecraft mc = Minecraft.getMinecraft(); mc.profiler.endStartSection("BTLayer"); - if (Shaders.isOptiFineShaderPackLoaded()) { + if (Mods.Optifine.isModLoaded()) { return renderGlobal.renderBlockLayer(blockRenderLayer, partialTicks, pass, entity); } diff --git a/src/main/java/gregtech/client/utils/DepthTextureUtil.java b/src/main/java/gregtech/client/utils/DepthTextureUtil.java index 3aa59bf75b4..d3bd1c4f06d 100644 --- a/src/main/java/gregtech/client/utils/DepthTextureUtil.java +++ b/src/main/java/gregtech/client/utils/DepthTextureUtil.java @@ -1,6 +1,6 @@ package gregtech.client.utils; -import gregtech.client.shader.Shaders; +import gregtech.api.util.Mods; import gregtech.common.ConfigHolder; import net.minecraft.client.Minecraft; @@ -40,7 +40,7 @@ public class DepthTextureUtil { private static int lastWidth, lastHeight; private static boolean shouldRenderDepthTexture() { - return lastBind && !Shaders.isOptiFineShaderPackLoaded() && ConfigHolder.client.hookDepthTexture && + return lastBind && !Mods.Optifine.isModLoaded() && ConfigHolder.client.hookDepthTexture && OpenGlHelper.isFramebufferEnabled(); } diff --git a/src/main/java/gregtech/client/utils/ItemRenderCompat.java b/src/main/java/gregtech/client/utils/ItemRenderCompat.java new file mode 100644 index 00000000000..4ea385c8910 --- /dev/null +++ b/src/main/java/gregtech/client/utils/ItemRenderCompat.java @@ -0,0 +1,169 @@ +package gregtech.client.utils; + +import gregtech.api.util.GTLog; +import gregtech.api.util.Mods; +import gregtech.api.util.world.DummyWorld; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +import appeng.items.misc.ItemEncodedPattern; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.util.List; + +public final class ItemRenderCompat { + + private static @Nullable ItemRenderCompat.RepresentativeStackExtractor rsHandler; + private static @Nullable ItemRenderCompat.RepresentativeStackExtractor ae2Handler; + + private ItemRenderCompat() {} + + @ApiStatus.Internal + public static void init() { + ae2Handler = AE2StackExtractor.create(); + rsHandler = RSStackExtractor.create(); + } + + /** + * Attempts to retrieve the actual ItemStack another stack represents. + *

+ * Primarily used to retrieve the output stack from AE2 or RS Patterns. + * + * @param stack the stack to retrieve from + * @return the actual represented ItemStack + */ + public static @NotNull ItemStack getRepresentedStack(@NotNull ItemStack stack) { + if (ae2Handler != null && ae2Handler.canHandleStack(stack)) { + return ae2Handler.getActualStack(stack); + } + if (rsHandler != null && rsHandler.canHandleStack(stack)) { + return rsHandler.getActualStack(stack); + } + return stack; + } + + /** + * An extractor to retrieve a represented stack from an ItemStack + */ + public interface RepresentativeStackExtractor { + + /** + * @param stack the stack to test + * @return if the extractor can handle the stack + */ + boolean canHandleStack(@NotNull ItemStack stack); + + /** + * @param stack the stack to retrieve from + * @return the represented stack + */ + @NotNull + ItemStack getActualStack(@NotNull ItemStack stack); + } + + /** + * Extracts the output stack from AE2 Patterns + */ + private static final class AE2StackExtractor implements RepresentativeStackExtractor { + + public static @Nullable ItemRenderCompat.AE2StackExtractor create() { + if (!Mods.AppliedEnergistics2.isModLoaded()) return null; + GTLog.logger.info("AppliedEnergistics2 found; enabling render integration."); + return new AE2StackExtractor(); + } + + @Override + public boolean canHandleStack(@NotNull ItemStack stack) { + return stack.getItem() instanceof ItemEncodedPattern; + } + + @Override + public @NotNull ItemStack getActualStack(@NotNull ItemStack stack) { + if (stack.isEmpty()) return ItemStack.EMPTY; + if (stack.getItem() instanceof ItemEncodedPattern encodedPattern) { + return encodedPattern.getOutput(stack); + } + return stack; + } + } + + /** + * Extracts the output stack from RS Patterns + */ + @SuppressWarnings("ClassCanBeRecord") + private static final class RSStackExtractor implements RepresentativeStackExtractor { + + private final MethodHandle getPatternFromCacheHandle; + private final MethodHandle getOutputsHandle; + private final Class itemPatternClass; + + private RSStackExtractor(MethodHandle getPatternFromCacheHandle, MethodHandle getOutputsHandle, + Class itemPatternClass) { + this.getPatternFromCacheHandle = getPatternFromCacheHandle; + this.getOutputsHandle = getOutputsHandle; + this.itemPatternClass = itemPatternClass; + } + + public static @Nullable ItemRenderCompat.RSStackExtractor create() { + if (!Mods.RefinedStorage.isModLoaded()) return null; + + Class clazz; + try { + clazz = Class.forName("com.raoulvdberge.refinedstorage.item.ItemPattern"); + GTLog.logger.info("RefinedStorage found; enabling render integration."); + } catch (ClassNotFoundException ignored) { + GTLog.logger.error("RefinedStorage classes not found; skipping render integration."); + return null; + } + + try { + Method method = clazz.getMethod("getPatternFromCache", World.class, ItemStack.class); + + MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + + MethodHandle getPatternFromCacheHandle = lookup.unreflect(method); + + method = method.getReturnType().getMethod("getOutputs"); + MethodHandle getOutputsHandle = lookup.unreflect(method); + + return new RSStackExtractor(getPatternFromCacheHandle, getOutputsHandle, clazz); + } catch (NoSuchMethodException | IllegalAccessException e) { + GTLog.logger.error("Failed to enable RefinedStorage integration", e); + return null; + } + } + + @Override + public boolean canHandleStack(@NotNull ItemStack stack) { + return itemPatternClass.isAssignableFrom(stack.getItem().getClass()); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull ItemStack getActualStack(@NotNull ItemStack stack) { + if (stack.isEmpty()) return ItemStack.EMPTY; + + List outputs; + try { + // ItemPattern.getPatternFromCache: (World, ItemStack) -> CraftingPattern + Object craftingPattern = getPatternFromCacheHandle.invoke(DummyWorld.INSTANCE, stack); + // CraftingPattern#getOutputs: () -> List + outputs = (List) getOutputsHandle.invoke(craftingPattern); + } catch (Throwable e) { + GTLog.logger.error("Failed to obtain item from ItemPattern", e); + return stack; + } + + if (outputs.isEmpty()) { + return stack; + } + return outputs.get(0); + } + } +} diff --git a/src/main/java/gregtech/client/utils/RenderUtil.java b/src/main/java/gregtech/client/utils/RenderUtil.java index ed91be4e57e..070ff054c10 100644 --- a/src/main/java/gregtech/client/utils/RenderUtil.java +++ b/src/main/java/gregtech/client/utils/RenderUtil.java @@ -153,6 +153,9 @@ public static void useStencil(Runnable mask, Runnable renderInMask, boolean shou renderInMask.run(); GL11.glDisable(GL11.GL_STENCIL_TEST); + GL11.glClearStencil(0); + GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT); + GL11.glStencilFunc(GL11.GL_ALWAYS, 0, 0xFF); } public static void useLightMap(float x, float y, Runnable codeBlock) { diff --git a/src/main/java/gregtech/common/CommonProxy.java b/src/main/java/gregtech/common/CommonProxy.java index 7cac0f93204..b9927847cd4 100644 --- a/src/main/java/gregtech/common/CommonProxy.java +++ b/src/main/java/gregtech/common/CommonProxy.java @@ -153,6 +153,8 @@ public static void registerBlocks(RegistryEvent.Register event) { registry.register(RUBBER_WOOD_DOOR); registry.register(TREATED_WOOD_DOOR); registry.register(BRITTLE_CHARCOAL); + registry.register(POWDERBARREL); + registry.register(ITNT); registry.register(METAL_SHEET); registry.register(LARGE_METAL_SHEET); registry.register(STUDS); @@ -273,6 +275,8 @@ public static void registerItems(RegistryEvent.Register event) { registry.register(createItemBlock(RUBBER_LOG, ItemBlock::new)); registry.register(createItemBlock(RUBBER_LEAVES, ItemBlock::new)); registry.register(createItemBlock(RUBBER_SAPLING, ItemBlock::new)); + registry.register(createItemBlock(POWDERBARREL, ItemBlock::new)); + registry.register(createItemBlock(ITNT, ItemBlock::new)); for (BlockCompressed block : COMPRESSED_BLOCKS) { registry.register(createItemBlock(block, b -> new MaterialItemBlock(b, OrePrefix.block))); @@ -297,9 +301,9 @@ public static void initComponents(RegistryEvent.Register event) { @SubscribeEvent public static void registerRecipes(RegistryEvent.Register event) { // Registers Fusion tiers for the FusionEUToStartProperty - FusionEUToStartProperty.registerFusionTier(6, "(MK1)"); - FusionEUToStartProperty.registerFusionTier(7, "(MK2)"); - FusionEUToStartProperty.registerFusionTier(8, "(MK3)"); + FusionEUToStartProperty.registerFusionTier(GTValues.LuV, "(MK1)"); + FusionEUToStartProperty.registerFusionTier(GTValues.ZPM, "(MK2)"); + FusionEUToStartProperty.registerFusionTier(GTValues.UV, "(MK3)"); // Register data stick copying custom scanner logic AssemblyLineManager.registerScannerLogic(); diff --git a/src/main/java/gregtech/common/ConfigHolder.java b/src/main/java/gregtech/common/ConfigHolder.java index 54863fecb57..290fb9f7d64 100644 --- a/src/main/java/gregtech/common/ConfigHolder.java +++ b/src/main/java/gregtech/common/ConfigHolder.java @@ -156,6 +156,11 @@ public static class MachineOptions { "Default: false" }) @Config.RequiresMcRestart public boolean highTierContent = false; + + @Config.Comment({ "Whether tick acceleration effects are allowed to affect GT machines.", + "This does NOT apply to the World Accelerator, but to external effects like Time in a Bottle.", + "Default: true" }) + public boolean allowTickAcceleration = true; } public static class WorldGenOptions { diff --git a/src/main/java/gregtech/common/EventHandlers.java b/src/main/java/gregtech/common/EventHandlers.java index da32dd83ca5..588909961e5 100644 --- a/src/main/java/gregtech/common/EventHandlers.java +++ b/src/main/java/gregtech/common/EventHandlers.java @@ -14,6 +14,7 @@ import gregtech.api.util.GTUtility; import gregtech.api.util.VirtualTankRegistry; import gregtech.api.worldgen.bedrockFluids.BedrockFluidVeinSaveData; +import gregtech.common.entities.EntityGTExplosive; import gregtech.common.items.MetaItems; import gregtech.common.items.armor.IStepAssist; import gregtech.common.items.armor.PowerlessJetpack; @@ -26,6 +27,7 @@ import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.attributes.AttributeModifier; import net.minecraft.entity.ai.attributes.IAttributeInstance; +import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.monster.EntityEnderman; import net.minecraft.entity.monster.EntityZombie; import net.minecraft.entity.player.EntityPlayer; @@ -48,6 +50,7 @@ import net.minecraftforge.event.entity.player.AdvancementEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.furnace.FurnaceFuelBurnTimeEvent; +import net.minecraftforge.event.world.ExplosionEvent; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.fluids.FluidUtil; import net.minecraftforge.fml.common.Mod; @@ -373,4 +376,13 @@ public static void onFurnaceFuelBurnTime(FurnaceFuelBurnTimeEvent event) { event.setBurnTime(6400); } } + + @SubscribeEvent + public static void onExplosionDetonate(ExplosionEvent.Detonate event) { + if (event.getExplosion().exploder instanceof EntityGTExplosive explosive) { + if (explosive.dropsAllBlocks()) { + event.getAffectedEntities().removeIf(entity -> entity instanceof EntityItem); + } + } + } } diff --git a/src/main/java/gregtech/common/MetaEntities.java b/src/main/java/gregtech/common/MetaEntities.java index 00efb35f87d..965ce3bfb18 100644 --- a/src/main/java/gregtech/common/MetaEntities.java +++ b/src/main/java/gregtech/common/MetaEntities.java @@ -4,10 +4,13 @@ import gregtech.api.util.GTUtility; import gregtech.client.renderer.handler.DynamiteRenderer; import gregtech.client.renderer.handler.GTBoatRenderer; +import gregtech.client.renderer.handler.GTExplosiveRenderer; import gregtech.client.renderer.handler.PortalRenderer; import gregtech.common.entities.DynamiteEntity; import gregtech.common.entities.GTBoatEntity; +import gregtech.common.entities.ITNTEntity; import gregtech.common.entities.PortalEntity; +import gregtech.common.entities.PowderbarrelEntity; import net.minecraft.client.Minecraft; import net.minecraftforge.fml.client.registry.RenderingRegistry; @@ -24,6 +27,10 @@ public static void init() { GregTechAPI.instance, 64, 5, true); EntityRegistry.registerModEntity(GTUtility.gregtechId("gtboat"), GTBoatEntity.class, "GTBoat", 3, GregTechAPI.instance, 64, 2, true); + EntityRegistry.registerModEntity(GTUtility.gregtechId("powderbarrel"), PowderbarrelEntity.class, "Powderbarrel", + 4, GregTechAPI.instance, 64, 3, true); + EntityRegistry.registerModEntity(GTUtility.gregtechId("itnt"), ITNTEntity.class, "ITNT", 5, + GregTechAPI.instance, 64, 3, true); } @SideOnly(Side.CLIENT) @@ -32,5 +39,7 @@ public static void initRenderers() { manager -> new DynamiteRenderer(manager, Minecraft.getMinecraft().getRenderItem())); RenderingRegistry.registerEntityRenderingHandler(PortalEntity.class, PortalRenderer::new); RenderingRegistry.registerEntityRenderingHandler(GTBoatEntity.class, GTBoatRenderer::new); + RenderingRegistry.registerEntityRenderingHandler(PowderbarrelEntity.class, GTExplosiveRenderer::new); + RenderingRegistry.registerEntityRenderingHandler(ITNTEntity.class, GTExplosiveRenderer::new); } } diff --git a/src/main/java/gregtech/common/ToolEventHandlers.java b/src/main/java/gregtech/common/ToolEventHandlers.java index 65d407e9635..62195e49791 100644 --- a/src/main/java/gregtech/common/ToolEventHandlers.java +++ b/src/main/java/gregtech/common/ToolEventHandlers.java @@ -477,6 +477,8 @@ private static void drawGridOverlays(@NotNull AxisAlignedBB box) { @SideOnly(Side.CLIENT) private static void drawGridOverlays(EnumFacing facing, AxisAlignedBB box, Predicate test) { + if (facing == null) return; + Tessellator tessellator = Tessellator.getInstance(); BufferBuilder buffer = tessellator.getBuffer(); buffer.begin(3, DefaultVertexFormats.POSITION_COLOR); diff --git a/src/main/java/gregtech/common/blocks/BlockAsphalt.java b/src/main/java/gregtech/common/blocks/BlockAsphalt.java index 281e47c76b8..728245fcd90 100644 --- a/src/main/java/gregtech/common/blocks/BlockAsphalt.java +++ b/src/main/java/gregtech/common/blocks/BlockAsphalt.java @@ -1,8 +1,8 @@ package gregtech.common.blocks; -import gregtech.api.GregTechAPI; import gregtech.api.block.IStateHarvestLevel; import gregtech.api.block.VariantBlock; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; @@ -23,7 +23,7 @@ public BlockAsphalt() { setResistance(10.0f); setSoundType(SoundType.STONE); setDefaultState(getState(BlockType.ASPHALT)); - setCreativeTab(GregTechAPI.TAB_GREGTECH_DECORATIONS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_DECORATIONS); } @Override diff --git a/src/main/java/gregtech/common/blocks/BlockCompressed.java b/src/main/java/gregtech/common/blocks/BlockCompressed.java index 18b71b293d6..9f2fb91fb16 100644 --- a/src/main/java/gregtech/common/blocks/BlockCompressed.java +++ b/src/main/java/gregtech/common/blocks/BlockCompressed.java @@ -1,6 +1,5 @@ package gregtech.common.blocks; -import gregtech.api.GregTechAPI; import gregtech.api.items.toolitem.ToolClasses; import gregtech.api.unification.material.Material; import gregtech.api.unification.material.info.MaterialIconType; @@ -9,6 +8,7 @@ import gregtech.client.model.modelfactories.MaterialBlockModelLoader; import gregtech.common.ConfigHolder; import gregtech.common.blocks.properties.PropertyMaterial; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.SoundType; import net.minecraft.block.state.IBlockState; @@ -46,7 +46,7 @@ private BlockCompressed() { setTranslationKey("compressed"); setHardness(5.0f); setResistance(10.0f); - setCreativeTab(GregTechAPI.TAB_GREGTECH_MATERIALS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_MATERIALS); } @Override diff --git a/src/main/java/gregtech/common/blocks/BlockFrame.java b/src/main/java/gregtech/common/blocks/BlockFrame.java index f4bb81f675d..88bbf3c2374 100644 --- a/src/main/java/gregtech/common/blocks/BlockFrame.java +++ b/src/main/java/gregtech/common/blocks/BlockFrame.java @@ -1,6 +1,5 @@ package gregtech.common.blocks; -import gregtech.api.GregTechAPI; import gregtech.api.items.toolitem.ToolClasses; import gregtech.api.items.toolitem.ToolHelper; import gregtech.api.pipenet.block.BlockPipe; @@ -15,6 +14,7 @@ import gregtech.client.model.modelfactories.MaterialBlockModelLoader; import gregtech.common.ConfigHolder; import gregtech.common.blocks.properties.PropertyMaterial; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.Block; import net.minecraft.block.SoundType; @@ -68,7 +68,7 @@ private BlockFrame() { setTranslationKey("frame"); setHardness(3.0f); setResistance(6.0f); - setCreativeTab(GregTechAPI.TAB_GREGTECH_MATERIALS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_MATERIALS); } @Override diff --git a/src/main/java/gregtech/common/blocks/BlockGregStairs.java b/src/main/java/gregtech/common/blocks/BlockGregStairs.java index f04cddbe6df..e8b2a12f2c7 100644 --- a/src/main/java/gregtech/common/blocks/BlockGregStairs.java +++ b/src/main/java/gregtech/common/blocks/BlockGregStairs.java @@ -1,7 +1,7 @@ package gregtech.common.blocks; -import gregtech.api.GregTechAPI; import gregtech.api.items.toolitem.ToolClasses; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.BlockStairs; import net.minecraft.block.state.IBlockState; @@ -15,7 +15,7 @@ public class BlockGregStairs extends BlockStairs { public BlockGregStairs(IBlockState state) { super(state); - setCreativeTab(GregTechAPI.TAB_GREGTECH_DECORATIONS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_DECORATIONS); this.useNeighborBrightness = true; this.setHarvestLevel(ToolClasses.AXE, 0); } diff --git a/src/main/java/gregtech/common/blocks/BlockLamp.java b/src/main/java/gregtech/common/blocks/BlockLamp.java index 3cc7c273ddc..28d26cfd41a 100644 --- a/src/main/java/gregtech/common/blocks/BlockLamp.java +++ b/src/main/java/gregtech/common/blocks/BlockLamp.java @@ -1,13 +1,13 @@ package gregtech.common.blocks; import gregtech.api.GTValues; -import gregtech.api.GregTechAPI; import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.material.MarkerMaterials; import gregtech.api.unification.ore.OrePrefix; import gregtech.client.model.lamp.LampBakedModel; import gregtech.client.model.lamp.LampModelType; import gregtech.client.utils.BloomEffectUtil; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.Block; import net.minecraft.block.SoundType; @@ -66,7 +66,7 @@ public BlockLamp(EnumDyeColor color) { .withProperty(LIGHT, true) .withProperty(INVERTED, false) .withProperty(POWERED, false)); - setCreativeTab(GregTechAPI.TAB_GREGTECH_DECORATIONS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_DECORATIONS); } public boolean isLightEnabled(ItemStack stack) { diff --git a/src/main/java/gregtech/common/blocks/BlockOre.java b/src/main/java/gregtech/common/blocks/BlockOre.java index c0fc037097b..db69901b8ff 100644 --- a/src/main/java/gregtech/common/blocks/BlockOre.java +++ b/src/main/java/gregtech/common/blocks/BlockOre.java @@ -1,6 +1,5 @@ package gregtech.common.blocks; -import gregtech.api.GregTechAPI; import gregtech.api.items.toolitem.ToolClasses; import gregtech.api.unification.material.Material; import gregtech.api.unification.material.info.MaterialFlags; @@ -13,6 +12,7 @@ import gregtech.client.model.OreBakedModel; import gregtech.client.utils.BloomEffectUtil; import gregtech.common.blocks.properties.PropertyStoneType; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.Block; import net.minecraft.block.SoundType; @@ -54,7 +54,7 @@ public BlockOre(Material material, StoneType[] allowedValues) { this.material = Objects.requireNonNull(material, "Material in BlockOre can not be null!"); STONE_TYPE = PropertyStoneType.create("stone_type", allowedValues); initBlockState(); - setCreativeTab(GregTechAPI.TAB_GREGTECH_ORES); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_ORES); } @NotNull @@ -173,7 +173,7 @@ public boolean isFireSource(@NotNull World world, @NotNull BlockPos pos, @NotNul @Override public void getSubBlocks(@NotNull CreativeTabs tab, @NotNull NonNullList list) { - if (tab == CreativeTabs.SEARCH || tab == GregTechAPI.TAB_GREGTECH_ORES) { + if (tab == CreativeTabs.SEARCH || tab == GTCreativeTabs.TAB_GREGTECH_ORES) { blockState.getValidStates().stream() .filter(state -> state.getValue(STONE_TYPE).shouldBeDroppedAsItem) .forEach(blockState -> list.add(GTUtility.toItem(blockState))); diff --git a/src/main/java/gregtech/common/blocks/BlockWarningSign.java b/src/main/java/gregtech/common/blocks/BlockWarningSign.java index e452e203528..4fdf97dd2fd 100644 --- a/src/main/java/gregtech/common/blocks/BlockWarningSign.java +++ b/src/main/java/gregtech/common/blocks/BlockWarningSign.java @@ -1,8 +1,8 @@ package gregtech.common.blocks; -import gregtech.api.GregTechAPI; import gregtech.api.block.VariantBlock; import gregtech.api.items.toolitem.ToolClasses; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; @@ -24,7 +24,7 @@ public BlockWarningSign() { setSoundType(SoundType.METAL); setHarvestLevel(ToolClasses.WRENCH, 1); setDefaultState(getState(SignType.YELLOW_STRIPES)); - setCreativeTab(GregTechAPI.TAB_GREGTECH_DECORATIONS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_DECORATIONS); } @Override diff --git a/src/main/java/gregtech/common/blocks/BlockWarningSign1.java b/src/main/java/gregtech/common/blocks/BlockWarningSign1.java index b25d74c9397..199a892d781 100644 --- a/src/main/java/gregtech/common/blocks/BlockWarningSign1.java +++ b/src/main/java/gregtech/common/blocks/BlockWarningSign1.java @@ -1,8 +1,8 @@ package gregtech.common.blocks; -import gregtech.api.GregTechAPI; import gregtech.api.block.VariantBlock; import gregtech.api.items.toolitem.ToolClasses; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.SoundType; import net.minecraft.block.material.Material; @@ -24,7 +24,7 @@ public BlockWarningSign1() { setSoundType(SoundType.METAL); setHarvestLevel(ToolClasses.WRENCH, 1); setDefaultState(getState(SignType.MOB_SPAWNER_HAZARD)); - setCreativeTab(GregTechAPI.TAB_GREGTECH_DECORATIONS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_DECORATIONS); } @Override diff --git a/src/main/java/gregtech/common/blocks/MetaBlocks.java b/src/main/java/gregtech/common/blocks/MetaBlocks.java index ada71a669ac..1cacda968e6 100644 --- a/src/main/java/gregtech/common/blocks/MetaBlocks.java +++ b/src/main/java/gregtech/common/blocks/MetaBlocks.java @@ -24,6 +24,8 @@ import gregtech.client.renderer.pipe.LaserPipeRenderer; import gregtech.client.renderer.pipe.OpticalPipeRenderer; import gregtech.common.ConfigHolder; +import gregtech.common.blocks.explosive.BlockITNT; +import gregtech.common.blocks.explosive.BlockPowderbarrel; import gregtech.common.blocks.foam.BlockFoam; import gregtech.common.blocks.foam.BlockPetrifiedFoam; import gregtech.common.blocks.wood.BlockGregFence; @@ -164,6 +166,8 @@ private MetaBlocks() {} public static BlockFenceGate TREATED_WOOD_FENCE_GATE; public static BlockWoodenDoor RUBBER_WOOD_DOOR; public static BlockWoodenDoor TREATED_WOOD_DOOR; + public static BlockPowderbarrel POWDERBARREL; + public static BlockITNT ITNT; public static BlockBrittleCharcoal BRITTLE_CHARCOAL; @@ -312,6 +316,10 @@ public static void init() { RUBBER_WOOD_DOOR.setRegistryName("rubber_wood_door").setTranslationKey("rubber_wood_door"); TREATED_WOOD_DOOR = new BlockWoodenDoor(() -> MetaItems.TREATED_WOOD_DOOR.getStackForm()); TREATED_WOOD_DOOR.setRegistryName("treated_wood_door").setTranslationKey("treated_wood_door"); + POWDERBARREL = new BlockPowderbarrel(); + POWDERBARREL.setRegistryName("powderbarrel").setTranslationKey("powderbarrel"); + ITNT = new BlockITNT(); + ITNT.setRegistryName("itnt").setTranslationKey("itnt"); BRITTLE_CHARCOAL = new BlockBrittleCharcoal(); BRITTLE_CHARCOAL.setRegistryName("brittle_charcoal"); @@ -481,6 +489,8 @@ public static void registerItemModels() { new ModelResourceLocation(Objects.requireNonNull(TREATED_WOOD_FENCE_GATE.getRegistryName()), "inventory")); registerItemModel(BRITTLE_CHARCOAL); + registerItemModel(POWDERBARREL); + registerItemModel(ITNT); registerItemModel(METAL_SHEET); registerItemModel(LARGE_METAL_SHEET); diff --git a/src/main/java/gregtech/common/blocks/StoneVariantBlock.java b/src/main/java/gregtech/common/blocks/StoneVariantBlock.java index 39194eea2a0..72fcb6d5180 100644 --- a/src/main/java/gregtech/common/blocks/StoneVariantBlock.java +++ b/src/main/java/gregtech/common/blocks/StoneVariantBlock.java @@ -1,11 +1,11 @@ package gregtech.common.blocks; -import gregtech.api.GregTechAPI; import gregtech.api.block.VariantBlock; import gregtech.api.items.toolitem.ToolClasses; import gregtech.api.unification.material.Material; import gregtech.api.unification.material.Materials; import gregtech.api.unification.ore.OrePrefix; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.SoundType; import net.minecraft.block.material.MapColor; @@ -40,7 +40,7 @@ public StoneVariantBlock(@NotNull StoneVariant stoneVariant) { setSoundType(SoundType.STONE); setHarvestLevel(ToolClasses.PICKAXE, 0); setDefaultState(getState(StoneType.BLACK_GRANITE)); - setCreativeTab(GregTechAPI.TAB_GREGTECH_DECORATIONS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_DECORATIONS); } @NotNull diff --git a/src/main/java/gregtech/common/blocks/explosive/BlockGTExplosive.java b/src/main/java/gregtech/common/blocks/explosive/BlockGTExplosive.java new file mode 100644 index 00000000000..e18a97abbbf --- /dev/null +++ b/src/main/java/gregtech/common/blocks/explosive/BlockGTExplosive.java @@ -0,0 +1,175 @@ +package gregtech.common.blocks.explosive; + +import gregtech.common.creativetab.GTCreativeTabs; +import gregtech.common.entities.EntityGTExplosive; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.projectile.EntityArrow; +import net.minecraft.init.Blocks; +import net.minecraft.init.Items; +import net.minecraft.init.SoundEvents; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.Explosion; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@SuppressWarnings("deprecation") +public abstract class BlockGTExplosive extends Block { + + private final boolean canRedstoneActivate; + private final boolean explodeOnMine; + private final int fuseLength; + + /** + * @param canRedstoneActivate whether redstone signal can prime this explosive + * @param explodeOnMine whether mining this block should prime it (sneak mine to drop normally) + * @param fuseLength explosion countdown after priming. Vanilla TNT is 80. + */ + public BlockGTExplosive(Material materialIn, boolean canRedstoneActivate, boolean explodeOnMine, int fuseLength) { + super(materialIn); + this.canRedstoneActivate = canRedstoneActivate; + this.explodeOnMine = explodeOnMine; + this.fuseLength = fuseLength; + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_TOOLS); + } + + protected abstract EntityGTExplosive createEntity(World world, BlockPos pos, EntityLivingBase exploder); + + @Override + public float getExplosionResistance(@NotNull Entity exploder) { + return 1.0f; + } + + @Override + public boolean canBeReplacedByLeaves(@NotNull IBlockState state, @NotNull IBlockAccess world, + @NotNull BlockPos pos) { + return false; + } + + @Override + public boolean isNormalCube(@NotNull IBlockState state) { + return true; + } + + @Override + public boolean canCreatureSpawn(@NotNull IBlockState state, @NotNull IBlockAccess world, @NotNull BlockPos pos, + @NotNull EntityLiving.SpawnPlacementType type) { + return false; + } + + @Override + public boolean canDropFromExplosion(@NotNull Explosion explosion) { + return false; + } + + public void explode(World world, BlockPos pos, EntityLivingBase exploder) { + if (!world.isRemote) { + EntityGTExplosive entity = createEntity(world, pos, exploder); + entity.setFuse(fuseLength); + world.spawnEntity(entity); + world.playSound(null, entity.posX, entity.posY, entity.posZ, SoundEvents.ENTITY_TNT_PRIMED, + SoundCategory.BLOCKS, 1.0f, 1.0f); + } + } + + @Override + public void onExplosionDestroy(@NotNull World world, @NotNull BlockPos pos, @NotNull Explosion explosion) { + if (!world.isRemote) { + EntityGTExplosive entity = createEntity(world, pos, explosion.getExplosivePlacedBy()); + entity.setFuse(world.rand.nextInt(fuseLength / 4) + fuseLength / 8); + world.spawnEntity(entity); + } + } + + @Override + public boolean onBlockActivated(@NotNull World world, @NotNull BlockPos pos, @NotNull IBlockState state, + @NotNull EntityPlayer player, @NotNull EnumHand hand, @NotNull EnumFacing facing, + float hitX, float hitY, float hitZ) { + ItemStack stack = player.getHeldItem(hand); + if (!stack.isEmpty() && (stack.getItem() == Items.FLINT_AND_STEEL || stack.getItem() == Items.FIRE_CHARGE)) { + this.explode(world, pos, player); + world.setBlockState(pos, Blocks.AIR.getDefaultState(), 11); + if (stack.getItem() == Items.FLINT_AND_STEEL) { + stack.damageItem(1, player); + } else if (!player.capabilities.isCreativeMode) { + stack.shrink(1); + } + return true; + } + return super.onBlockActivated(world, pos, state, player, hand, facing, hitX, hitY, hitZ); + } + + @Override + public void dropBlockAsItemWithChance(@NotNull World world, @NotNull BlockPos pos, @NotNull IBlockState state, + float chance, int fortune) { + if (explodeOnMine) { + EntityPlayer player = this.harvesters.get(); + if (!player.isSneaking()) { + this.explode(world, pos, player); + return; + } + } + super.dropBlockAsItemWithChance(world, pos, state, chance, fortune); + } + + @Override + public void onEntityCollision(@NotNull World world, @NotNull BlockPos pos, @NotNull IBlockState state, + @NotNull Entity entity) { + if (!world.isRemote && entity instanceof EntityArrow arrow) { + if (arrow.isBurning()) { + this.explode(world, pos, arrow.shootingEntity instanceof EntityLivingBase living ? living : null); + world.setBlockToAir(pos); + } + } + } + + @Override + public void onBlockAdded(@NotNull World world, @NotNull BlockPos pos, @NotNull IBlockState state) { + super.onBlockAdded(world, pos, state); + if (canRedstoneActivate) { + if (world.isBlockPowered(pos)) { + explode(world, pos, null); + world.setBlockToAir(pos); + } + } + } + + @Override + public void neighborChanged(@NotNull IBlockState state, @NotNull World world, @NotNull BlockPos pos, + @NotNull Block block, @NotNull BlockPos fromPos) { + if (canRedstoneActivate) { + if (world.isBlockPowered(pos)) { + explode(world, pos, null); + world.setBlockToAir(pos); + } + } + } + + @Override + public void addInformation(@NotNull ItemStack stack, @Nullable World world, @NotNull List tooltip, + @NotNull ITooltipFlag flag) { + if (explodeOnMine) { + tooltip.add(I18n.format("tile.gt_explosive.breaking_tooltip")); + } + if (!canRedstoneActivate) { + tooltip.add(I18n.format("tile.gt_explosive.lighting_tooltip")); + } + } +} diff --git a/src/main/java/gregtech/common/blocks/explosive/BlockITNT.java b/src/main/java/gregtech/common/blocks/explosive/BlockITNT.java new file mode 100644 index 00000000000..91a9614d4c4 --- /dev/null +++ b/src/main/java/gregtech/common/blocks/explosive/BlockITNT.java @@ -0,0 +1,40 @@ +package gregtech.common.blocks.explosive; + +import gregtech.common.entities.EntityGTExplosive; +import gregtech.common.entities.ITNTEntity; + +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class BlockITNT extends BlockGTExplosive { + + public BlockITNT() { + super(Material.TNT, true, true, 40); + setHardness(0); + setSoundType(SoundType.PLANT); + } + + @Override + protected EntityGTExplosive createEntity(World world, BlockPos pos, EntityLivingBase exploder) { + float x = pos.getX() + 0.5F, y = pos.getY(), z = pos.getZ() + 0.5F; + return new ITNTEntity(world, x, y, z, exploder); + } + + @Override + public void addInformation(@NotNull ItemStack stack, @Nullable World world, @NotNull List tooltip, + @NotNull ITooltipFlag flag) { + tooltip.add(I18n.format("tile.itnt.drops_tooltip")); + super.addInformation(stack, world, tooltip, flag); + } +} diff --git a/src/main/java/gregtech/common/blocks/explosive/BlockPowderbarrel.java b/src/main/java/gregtech/common/blocks/explosive/BlockPowderbarrel.java new file mode 100644 index 00000000000..a2549ad7ee5 --- /dev/null +++ b/src/main/java/gregtech/common/blocks/explosive/BlockPowderbarrel.java @@ -0,0 +1,42 @@ +package gregtech.common.blocks.explosive; + +import gregtech.api.items.toolitem.ToolClasses; +import gregtech.common.blocks.material.GTBlockMaterials; +import gregtech.common.entities.EntityGTExplosive; +import gregtech.common.entities.PowderbarrelEntity; + +import net.minecraft.block.SoundType; +import net.minecraft.client.resources.I18n; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class BlockPowderbarrel extends BlockGTExplosive { + + public BlockPowderbarrel() { + super(GTBlockMaterials.POWDERBARREL, false, true, 100); + setHarvestLevel(ToolClasses.AXE, 1); + setHardness(0.5F); + setSoundType(SoundType.WOOD); + } + + @Override + protected EntityGTExplosive createEntity(World world, BlockPos pos, EntityLivingBase exploder) { + float x = pos.getX() + 0.5F, y = pos.getY(), z = pos.getZ() + 0.5F; + return new PowderbarrelEntity(world, x, y, z, exploder); + } + + @Override + public void addInformation(@NotNull ItemStack stack, @Nullable World world, @NotNull List tooltip, + @NotNull ITooltipFlag flag) { + tooltip.add(I18n.format("tile.powderbarrel.drops_tooltip")); + super.addInformation(stack, world, tooltip, flag); + } +} diff --git a/src/main/java/gregtech/common/blocks/material/GTBlockMaterials.java b/src/main/java/gregtech/common/blocks/material/GTBlockMaterials.java new file mode 100644 index 00000000000..213ba58fdd9 --- /dev/null +++ b/src/main/java/gregtech/common/blocks/material/GTBlockMaterials.java @@ -0,0 +1,18 @@ +package gregtech.common.blocks.material; + +import net.minecraft.block.material.MapColor; +import net.minecraft.block.material.Material; + +public class GTBlockMaterials { + + public static final Material POWDERBARREL = new PowderbarrelMaterial(); + + private static class PowderbarrelMaterial extends Material { + + public PowderbarrelMaterial() { + super(MapColor.STONE); + setAdventureModeExempt(); + setImmovableMobility(); + } + } +} diff --git a/src/main/java/gregtech/common/blocks/wood/BlockGregFence.java b/src/main/java/gregtech/common/blocks/wood/BlockGregFence.java index 975c0708a22..ba3188cac78 100644 --- a/src/main/java/gregtech/common/blocks/wood/BlockGregFence.java +++ b/src/main/java/gregtech/common/blocks/wood/BlockGregFence.java @@ -1,7 +1,7 @@ package gregtech.common.blocks.wood; -import gregtech.api.GregTechAPI; import gregtech.api.items.toolitem.ToolClasses; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.BlockFence; import net.minecraft.block.SoundType; @@ -15,7 +15,7 @@ public BlockGregFence() { setHardness(2.0F); setResistance(5.0F); setSoundType(SoundType.WOOD); - setCreativeTab(GregTechAPI.TAB_GREGTECH_DECORATIONS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_DECORATIONS); setHarvestLevel(ToolClasses.AXE, 0); } } diff --git a/src/main/java/gregtech/common/blocks/wood/BlockGregFenceGate.java b/src/main/java/gregtech/common/blocks/wood/BlockGregFenceGate.java index 29c539eca35..aff946015e2 100644 --- a/src/main/java/gregtech/common/blocks/wood/BlockGregFenceGate.java +++ b/src/main/java/gregtech/common/blocks/wood/BlockGregFenceGate.java @@ -1,7 +1,7 @@ package gregtech.common.blocks.wood; -import gregtech.api.GregTechAPI; import gregtech.api.items.toolitem.ToolClasses; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.BlockFenceGate; import net.minecraft.block.BlockPlanks; @@ -14,7 +14,7 @@ public BlockGregFenceGate() { setHardness(2.0F); setResistance(5.0F); setSoundType(SoundType.WOOD); - setCreativeTab(GregTechAPI.TAB_GREGTECH_DECORATIONS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_DECORATIONS); setHarvestLevel(ToolClasses.AXE, 0); } } diff --git a/src/main/java/gregtech/common/blocks/wood/BlockGregWoodSlab.java b/src/main/java/gregtech/common/blocks/wood/BlockGregWoodSlab.java index acab4c9dd43..d2cc8e2b96e 100644 --- a/src/main/java/gregtech/common/blocks/wood/BlockGregWoodSlab.java +++ b/src/main/java/gregtech/common/blocks/wood/BlockGregWoodSlab.java @@ -1,8 +1,8 @@ package gregtech.common.blocks.wood; -import gregtech.api.GregTechAPI; import gregtech.api.items.toolitem.ToolClasses; import gregtech.common.blocks.MetaBlocks; +import gregtech.common.creativetab.GTCreativeTabs; import net.minecraft.block.BlockSlab; import net.minecraft.block.SoundType; @@ -35,7 +35,7 @@ public BlockGregWoodSlab() { setResistance(5.0F); setSoundType(SoundType.WOOD); setHarvestLevel(ToolClasses.AXE, 0); - setCreativeTab(GregTechAPI.TAB_GREGTECH_DECORATIONS); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH_DECORATIONS); this.useNeighborBrightness = true; } diff --git a/src/main/java/gregtech/common/blocks/wood/BlockRubberLeaves.java b/src/main/java/gregtech/common/blocks/wood/BlockRubberLeaves.java index 533cf4249d2..2149165afa4 100644 --- a/src/main/java/gregtech/common/blocks/wood/BlockRubberLeaves.java +++ b/src/main/java/gregtech/common/blocks/wood/BlockRubberLeaves.java @@ -1,7 +1,7 @@ package gregtech.common.blocks.wood; -import gregtech.api.GregTechAPI; import gregtech.common.blocks.MetaBlocks; +import gregtech.common.creativetab.GTCreativeTabs; import gregtech.core.CoreModule; import net.minecraft.block.BlockLeaves; @@ -31,7 +31,7 @@ public BlockRubberLeaves() { .withProperty(CHECK_DECAY, true) .withProperty(DECAYABLE, true)); setTranslationKey("rubber_leaves"); - setCreativeTab(GregTechAPI.TAB_GREGTECH); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH); Blocks.FIRE.setFireInfo(this, 30, 60); } diff --git a/src/main/java/gregtech/common/blocks/wood/BlockRubberLog.java b/src/main/java/gregtech/common/blocks/wood/BlockRubberLog.java index bd89801a699..7c2f2b09f85 100644 --- a/src/main/java/gregtech/common/blocks/wood/BlockRubberLog.java +++ b/src/main/java/gregtech/common/blocks/wood/BlockRubberLog.java @@ -1,7 +1,7 @@ package gregtech.common.blocks.wood; -import gregtech.api.GregTechAPI; import gregtech.api.items.toolitem.ToolClasses; +import gregtech.common.creativetab.GTCreativeTabs; import gregtech.common.items.MetaItems; import net.minecraft.block.BlockLog; @@ -27,7 +27,7 @@ public BlockRubberLog() { .withProperty(LOG_AXIS, BlockLog.EnumAxis.Y) .withProperty(NATURAL, false)); setTranslationKey("rubber_log"); - setCreativeTab(GregTechAPI.TAB_GREGTECH); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH); setHarvestLevel(ToolClasses.AXE, 0); } diff --git a/src/main/java/gregtech/common/blocks/wood/BlockRubberSapling.java b/src/main/java/gregtech/common/blocks/wood/BlockRubberSapling.java index cd9b35d8232..653a31fc9a8 100644 --- a/src/main/java/gregtech/common/blocks/wood/BlockRubberSapling.java +++ b/src/main/java/gregtech/common/blocks/wood/BlockRubberSapling.java @@ -1,6 +1,6 @@ package gregtech.common.blocks.wood; -import gregtech.api.GregTechAPI; +import gregtech.common.creativetab.GTCreativeTabs; import gregtech.common.worldgen.WorldGenRubberTree; import net.minecraft.block.BlockBush; @@ -28,7 +28,7 @@ public BlockRubberSapling() { this.setDefaultState(this.blockState.getBaseState() .withProperty(STAGE, 0)); setTranslationKey("rubber_sapling"); - setCreativeTab(GregTechAPI.TAB_GREGTECH); + setCreativeTab(GTCreativeTabs.TAB_GREGTECH); setHardness(0.0F); setSoundType(SoundType.PLANT); } diff --git a/src/main/java/gregtech/common/covers/CoverDigitalInterface.java b/src/main/java/gregtech/common/covers/CoverDigitalInterface.java index 0a04343acde..d4950ca4ac7 100644 --- a/src/main/java/gregtech/common/covers/CoverDigitalInterface.java +++ b/src/main/java/gregtech/common/covers/CoverDigitalInterface.java @@ -16,8 +16,10 @@ import gregtech.api.metatileentity.multiblock.MultiblockControllerBase; import gregtech.api.util.GTLog; import gregtech.api.util.Position; +import gregtech.api.util.TextFormattingUtil; import gregtech.client.renderer.texture.Textures; import gregtech.client.utils.RenderUtil; +import gregtech.common.metatileentities.multi.electric.MetaTileEntityPowerSubstation; import gregtech.common.terminal.app.prospector.widget.WidgetOreList; import net.minecraft.client.Minecraft; @@ -227,7 +229,7 @@ public void onAttachment(@NotNull CoverableView coverableView, @NotNull EnumFaci } else if (getItemCapability() != null) { items = new ItemStack[getItemCapability().getSlots()]; this.mode = MODE.ITEM; - } else if (getEnergyCapability() != null) { + } else if (getEnergyCapability() != null || getCoverableView() instanceof MetaTileEntityPowerSubstation) { this.mode = MODE.ENERGY; } else if (getMachineCapability() != null) { this.mode = MODE.MACHINE; @@ -578,22 +580,19 @@ private void syncAllInfo() { } } if (this.mode == MODE.ENERGY || (mode == MODE.PROXY && proxyMode[2] > 0)) { - IEnergyContainer energyContainer = this.getEnergyCapability(); - if (energyContainer != null) { - // TODO, figure out what to do when values exceed Long.MAX_VALUE, ie with multiple Ultimate batteries - if (energyStored != energyContainer.getEnergyStored() || - energyCapability != energyContainer.getEnergyCapacity()) { - energyStored = energyContainer.getEnergyStored(); - energyCapability = energyContainer.getEnergyCapacity(); + if (getCoverableView() instanceof MetaTileEntityPowerSubstation pss) { + if (energyStored != pss.getStoredLong() || energyCapability != pss.getCapacityLong()) { + energyStored = pss.getStoredLong(); + energyCapability = pss.getCapacityLong(); writeCustomData(GregtechDataCodes.UPDATE_ENERGY, packetBuffer -> { packetBuffer.writeLong(energyStored); packetBuffer.writeLong(energyCapability); }); } - if (this.getOffsetTimer() % 20 == 0) { // per second + if (this.getOffsetTimer() % 20 == 0) { writeCustomData(GregtechDataCodes.UPDATE_ENERGY_PER, packetBuffer -> { - packetBuffer.writeLong(energyContainer.getInputPerSec()); - packetBuffer.writeLong(energyContainer.getOutputPerSec()); + packetBuffer.writeLong(pss.getAverageInLastSec() * 20L); + packetBuffer.writeLong(pss.getAverageOutLastSec() * 20L); inputEnergyList.add(energyInputPerDur); outputEnergyList.add(energyOutputPerDur); if (inputEnergyList.size() > 13) { @@ -602,6 +601,33 @@ private void syncAllInfo() { } }); } + } else { + IEnergyContainer energyContainer = this.getEnergyCapability(); + if (energyContainer != null) { + // TODO, figure out what to do when values exceed Long.MAX_VALUE, ie with multiple Ultimate + // batteries + if (energyStored != energyContainer.getEnergyStored() || + energyCapability != energyContainer.getEnergyCapacity()) { + energyStored = energyContainer.getEnergyStored(); + energyCapability = energyContainer.getEnergyCapacity(); + writeCustomData(GregtechDataCodes.UPDATE_ENERGY, packetBuffer -> { + packetBuffer.writeLong(energyStored); + packetBuffer.writeLong(energyCapability); + }); + } + if (this.getOffsetTimer() % 20 == 0) { // per second + writeCustomData(GregtechDataCodes.UPDATE_ENERGY_PER, packetBuffer -> { + packetBuffer.writeLong(energyContainer.getInputPerSec()); + packetBuffer.writeLong(energyContainer.getOutputPerSec()); + inputEnergyList.add(energyInputPerDur); + outputEnergyList.add(energyOutputPerDur); + if (inputEnergyList.size() > 13) { + inputEnergyList.remove(0); + outputEnergyList.remove(0); + } + }); + } + } } } if (this.mode == MODE.MACHINE || (mode == MODE.PROXY && proxyMode[3] > 0)) { @@ -625,17 +651,28 @@ private void syncAllInfo() { }); } if (this.getOffsetTimer() % 20 == 0) { - IEnergyContainer energyContainer = this.getEnergyCapability(); - if (energyContainer != null) { - if (energyStored != energyContainer.getEnergyStored() || - energyCapability != energyContainer.getEnergyCapacity()) { - energyStored = energyContainer.getEnergyStored(); - energyCapability = energyContainer.getEnergyCapacity(); + if (getCoverableView() instanceof MetaTileEntityPowerSubstation pss) { + if (energyStored != pss.getStoredLong() || energyCapability != pss.getCapacityLong()) { + energyStored = pss.getStoredLong(); + energyCapability = pss.getCapacityLong(); writeCustomData(GregtechDataCodes.UPDATE_ENERGY, packetBuffer -> { packetBuffer.writeLong(energyStored); packetBuffer.writeLong(energyCapability); }); } + } else { + IEnergyContainer energyContainer = this.getEnergyCapability(); + if (energyContainer != null) { + if (energyStored != energyContainer.getEnergyStored() || + energyCapability != energyContainer.getEnergyCapacity()) { + energyStored = energyContainer.getEnergyStored(); + energyCapability = energyContainer.getEnergyCapacity(); + writeCustomData(GregtechDataCodes.UPDATE_ENERGY, packetBuffer -> { + packetBuffer.writeLong(energyStored); + packetBuffer.writeLong(energyCapability); + }); + } + } } } } @@ -860,6 +897,7 @@ public boolean canCapabilityAttach() { return getFluidCapability() != null || getItemCapability() != null || getEnergyCapability() != null || + getCoverableView() instanceof MetaTileEntityPowerSubstation || getMachineCapability() != null; } @@ -1046,9 +1084,11 @@ private void renderEnergyMode() { } RenderUtil.renderLineChart(inputEnergyList, max, -5.5f / 16, 5.5f / 16, 12f / 16, 6f / 16, 0.005f, 0XFF03FF00); RenderUtil.renderLineChart(outputEnergyList, max, -5.5f / 16, 5.5f / 16, 12f / 16, 6f / 16, 0.005f, 0XFFFF2F39); - RenderUtil.renderText(-5.7f / 16, -2.3f / 16, 0, 1.0f / 270, 0XFF03FF00, "EU I: " + energyInputPerDur + "EU/s", + RenderUtil.renderText(-5.7f / 16, -2.3f / 16, 0, 1.0f / 270, 0XFF03FF00, + "EU I: " + TextFormattingUtil.formatNumbers(energyInputPerDur / 20) + "EU/t", false); - RenderUtil.renderText(-5.7f / 16, -1.6f / 16, 0, 1.0f / 270, 0XFFFF0000, "EU O: " + energyOutputPerDur + "EU/s", + RenderUtil.renderText(-5.7f / 16, -1.6f / 16, 0, 1.0f / 270, 0XFFFF0000, + "EU O: " + TextFormattingUtil.formatNumbers(energyOutputPerDur / 20) + "EU/t", false); // Bandaid fix to prevent overflowing renders when dealing with items that cause long overflow, ie Ultimate // Battery diff --git a/src/main/java/gregtech/common/covers/CoverMachineController.java b/src/main/java/gregtech/common/covers/CoverMachineController.java index 8814da16ca4..423a44fccd3 100644 --- a/src/main/java/gregtech/common/covers/CoverMachineController.java +++ b/src/main/java/gregtech/common/covers/CoverMachineController.java @@ -3,23 +3,36 @@ import gregtech.api.capability.GregtechTileCapabilities; import gregtech.api.capability.IControllable; import gregtech.api.cover.*; -import gregtech.api.gui.GuiTextures; -import gregtech.api.gui.ModularUI; -import gregtech.api.gui.widgets.*; +import gregtech.api.mui.GTGuiTextures; +import gregtech.api.mui.GTGuis; import gregtech.client.renderer.texture.Textures; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; import net.minecraft.util.*; -import net.minecraftforge.items.ItemStackHandler; +import net.minecraft.util.text.TextFormatting; import codechicken.lib.raytracer.CuboidRayTraceResult; import codechicken.lib.render.CCRenderState; import codechicken.lib.render.pipeline.IVertexOperation; import codechicken.lib.vec.Cuboid6; import codechicken.lib.vec.Matrix4; +import com.cleanroommc.modularui.api.drawable.IKey; +import com.cleanroommc.modularui.drawable.ItemDrawable; +import com.cleanroommc.modularui.drawable.Rectangle; +import com.cleanroommc.modularui.factory.SidedPosGuiData; +import com.cleanroommc.modularui.screen.ModularPanel; +import com.cleanroommc.modularui.utils.Alignment; +import com.cleanroommc.modularui.value.BoolValue; +import com.cleanroommc.modularui.value.sync.BooleanSyncValue; +import com.cleanroommc.modularui.value.sync.EnumSyncValue; +import com.cleanroommc.modularui.value.sync.GuiSyncManager; +import com.cleanroommc.modularui.widget.Widget; +import com.cleanroommc.modularui.widgets.ToggleButton; +import com.cleanroommc.modularui.widgets.layout.Column; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,23 +41,16 @@ public class CoverMachineController extends CoverBase implements CoverWithUI { - private int minRedstoneStrength; private boolean isInverted; private ControllerMode controllerMode; - private final ItemStackHandler displayInventory = new ItemStackHandler(1); public CoverMachineController(@NotNull CoverDefinition definition, @NotNull CoverableView coverableView, @NotNull EnumFacing attachedSide) { super(definition, coverableView, attachedSide); - this.minRedstoneStrength = 1; this.isInverted = false; this.controllerMode = ControllerMode.MACHINE; } - public int getMinRedstoneStrength() { - return minRedstoneStrength; - } - public ControllerMode getControllerMode() { return controllerMode; } @@ -53,12 +59,6 @@ public boolean isInverted() { return isInverted; } - public void setMinRedstoneStrength(int minRedstoneStrength) { - this.minRedstoneStrength = minRedstoneStrength; - updateRedstoneStatus(); - getCoverableView().markDirty(); - } - public void setInverted(boolean inverted) { isInverted = inverted; updateRedstoneStatus(); @@ -69,21 +69,12 @@ public void setControllerMode(ControllerMode controllerMode) { resetCurrentControllable(); this.controllerMode = controllerMode; updateRedstoneStatus(); - updateDisplayInventory(); getCoverableView().markDirty(); } - private void cycleNextControllerMode() { - List allowedModes = getAllowedModes(getCoverableView(), getAttachedSide()); - int nextIndex = allowedModes.indexOf(controllerMode) + 1; - if (!allowedModes.isEmpty()) { - setControllerMode(allowedModes.get(nextIndex % allowedModes.size())); - } - } - public List getAllowedModes(@NotNull CoverableView coverable, @NotNull EnumFacing side) { List results = new ArrayList<>(); - for (ControllerMode controllerMode : ControllerMode.values()) { + for (ControllerMode controllerMode : ControllerMode.VALUES) { IControllable controllable = null; if (controllerMode.side == null) { controllable = coverable.getCapability(GregtechTileCapabilities.CAPABILITY_CONTROLLABLE, side); @@ -115,21 +106,111 @@ public boolean canAttach(@NotNull CoverableView coverable, @NotNull EnumFacing s } @Override - public ModularUI createUI(EntityPlayer player) { - updateDisplayInventory(); - return ModularUI.builder(GuiTextures.BACKGROUND, 176, 95) - .image(4, 4, 16, 16, GuiTextures.COVER_MACHINE_CONTROLLER) - .label(24, 8, "cover.machine_controller.title") - .widget(new SliderWidget("cover.machine_controller.redstone", 10, 24, 156, 20, 1.0f, 15.0f, - minRedstoneStrength, it -> setMinRedstoneStrength((int) it))) - .widget(new ClickButtonWidget(10, 48, 134, 18, "", data -> cycleNextControllerMode())) - .widget(new SimpleTextWidget(77, 58, "", 0xFFFFFF, () -> getControllerMode().getName()).setShadow(true)) - .widget(new SlotWidget(displayInventory, 0, 148, 48, false, false) - .setBackgroundTexture(GuiTextures.SLOT)) - .widget(new CycleButtonWidget(48, 70, 80, 18, this::isInverted, this::setInverted, - "cover.machine_controller.normal", "cover.machine_controller.inverted") - .setTooltipHoverString("cover.machine_controller.inverted.description")) - .build(this, player); + public boolean usesMui2() { + return true; + } + + @Override + public ModularPanel buildUI(SidedPosGuiData guiData, GuiSyncManager guiSyncManager) { + EnumSyncValue controllerModeValue = new EnumSyncValue<>(ControllerMode.class, + this::getControllerMode, this::setControllerMode); + BooleanSyncValue invertedValue = new BooleanSyncValue(this::isInverted, this::setInverted); + + guiSyncManager.syncValue("controller_mode", controllerModeValue); + guiSyncManager.syncValue("inverted", invertedValue); + + return GTGuis.createPanel(this, 176, 112) + .child(createTitleRow()) + .child(new Column() + .widthRel(1.0f).margin(7, 0) + .top(24).coverChildrenHeight() + + // Inverted mode + .child(createSettingsRow() + .child(new ToggleButton() + .size(16).left(0) + .value(new BoolValue.Dynamic(invertedValue::getValue, + $ -> invertedValue.setValue(true))) + .overlay(GTGuiTextures.BUTTON_REDSTONE_ON) + .selectedBackground(GTGuiTextures.MC_BUTTON_DISABLED)) + .child(IKey.lang("cover.machine_controller.enable_with_redstone").asWidget() + .heightRel(1.0f).left(20))) + .child(createSettingsRow() + .child(new ToggleButton() + .size(16).left(0) + .value(new BoolValue.Dynamic(() -> !invertedValue.getValue(), + $ -> invertedValue.setValue(false))) + .overlay(GTGuiTextures.BUTTON_REDSTONE_OFF) + .selectedBackground(GTGuiTextures.MC_BUTTON_DISABLED)) + .child(IKey.lang("cover.machine_controller.disable_with_redstone").asWidget() + .heightRel(1.0f).left(20))) + + // Separating line + .child(new Rectangle().setColor(UI_TEXT_COLOR).asWidget() + .height(1).widthRel(0.9f).alignX(0.5f).marginBottom(4).marginTop(4)) + + // Controlling selector + .child(createSettingsRow().height(16 + 2 + 16) + .child(new Column().heightRel(1.0f).coverChildrenWidth() + .child(IKey.lang("cover.machine_controller.control").asWidget() + .left(0).height(16).marginBottom(2)) + .child(modeButton(controllerModeValue, ControllerMode.MACHINE).left(0))) + .child(modeColumn(controllerModeValue, ControllerMode.COVER_UP, IKey.str("U")) + .right(100)) + .child(modeColumn(controllerModeValue, ControllerMode.COVER_DOWN, IKey.str("D")) + .right(80)) + .child(modeColumn(controllerModeValue, ControllerMode.COVER_NORTH, IKey.str("N")) + .right(60)) + .child(modeColumn(controllerModeValue, ControllerMode.COVER_SOUTH, IKey.str("S")) + .right(40)) + .child(modeColumn(controllerModeValue, ControllerMode.COVER_EAST, IKey.str("E")) + .right(20)) + .child(modeColumn(controllerModeValue, ControllerMode.COVER_WEST, IKey.str("W")) + .right(0)))); + } + + private Column modeColumn(EnumSyncValue syncValue, ControllerMode mode, IKey title) { + return new Column().coverChildrenHeight().width(18) + .child(title.asWidget().size(16).marginBottom(2).alignment(Alignment.Center)) + .child(modeButton(syncValue, mode)); + } + + private Widget modeButton(EnumSyncValue syncValue, ControllerMode mode) { + IControllable controllable = getControllable(mode); + if (controllable == null) { + // Nothing to control, put a placeholder widget + // 3 states possible here: + IKey detail; + if (mode.side == getAttachedSide()) { + // our own side, we can't control ourselves + detail = IKey.lang("cover.machine_controller.this_cover"); + } else if (mode.side != null) { + // some potential cover that either doesn't exist or isn't controllable + detail = IKey.lang("cover.machine_controller.cover_not_controllable"); + } else { + // cover holder is not controllable + detail = IKey.lang("cover.machine_controller.machine_not_controllable"); + } + + return GTGuiTextures.MC_BUTTON.asWidget().size(18) + .overlay(GTGuiTextures.BUTTON_CROSS) + .tooltip(t -> t.addLine(IKey.lang(mode.localeName)).addLine(detail)); + } + + ItemStack stack; + if (mode == ControllerMode.MACHINE) { + stack = getCoverableView().getStackForm(); + } else { + // this can't be null because we already checked IControllable, and it was not null + // noinspection ConstantConditions + stack = getCoverableView().getCoverAtSide(mode.side).getDefinition().getDropItemStack(); + } + + return new ToggleButton().size(18) + .value(boolValueOf(syncValue, mode)) + .overlay(new ItemDrawable(stack).asIcon().size(16)) + .tooltip(t -> t.addLine(IKey.lang(mode.localeName)) + .addLine(IKey.str(TextFormatting.GRAY + stack.getDisplayName()))); } @Override @@ -163,20 +244,8 @@ public void onRedstoneInputSignalChange(int newSignalStrength) { updateRedstoneStatus(); } - private void updateDisplayInventory() { - EnumFacing controlledSide = getControllerMode().side; - ItemStack resultStack = ItemStack.EMPTY; - if (controlledSide != null) { - Cover cover = getCoverableView().getCoverAtSide(controlledSide); - if (cover != null) { - resultStack = cover.getDefinition().getDropItemStack(); - } - } - this.displayInventory.setStackInSlot(0, resultStack); - } - - private @Nullable IControllable getControllable() { - EnumFacing side = controllerMode.side; + private @Nullable IControllable getControllable(ControllerMode mode) { + EnumFacing side = mode.side; if (side == null) { return getCoverableView().getCapability(GregtechTileCapabilities.CAPABILITY_CONTROLLABLE, getAttachedSide()); @@ -190,24 +259,22 @@ private void updateDisplayInventory() { } private void resetCurrentControllable() { - IControllable controllable = getControllable(); + IControllable controllable = getControllable(controllerMode); if (controllable != null) { controllable.setWorkingEnabled(doesOtherAllowingWork()); } } private void updateRedstoneStatus() { - IControllable controllable = getControllable(); + IControllable controllable = getControllable(controllerMode); if (controllable != null) { controllable.setWorkingEnabled(shouldAllowWorking() && doesOtherAllowingWork()); } } private boolean shouldAllowWorking() { - boolean shouldAllowWorking = getCoverableView().getInputRedstoneSignal(getAttachedSide(), true) < - minRedstoneStrength; - // noinspection SimplifiableConditionalExpression - return isInverted ? !shouldAllowWorking : shouldAllowWorking; + int inputSignal = getCoverableView().getInputRedstoneSignal(getAttachedSide(), true); + return isInverted ? inputSignal > 0 : inputSignal == 0; } private boolean doesOtherAllowingWork() { @@ -228,7 +295,6 @@ private boolean doesOtherAllowingWork() { @Override public void writeToNBT(@NotNull NBTTagCompound tagCompound) { super.writeToNBT(tagCompound); - tagCompound.setInteger("MinRedstoneStrength", minRedstoneStrength); tagCompound.setBoolean("Inverted", isInverted); tagCompound.setInteger("ControllerMode", controllerMode.ordinal()); } @@ -236,9 +302,22 @@ public void writeToNBT(@NotNull NBTTagCompound tagCompound) { @Override public void readFromNBT(@NotNull NBTTagCompound tagCompound) { super.readFromNBT(tagCompound); - this.minRedstoneStrength = tagCompound.getInteger("MinRedstoneStrength"); this.isInverted = tagCompound.getBoolean("Inverted"); - this.controllerMode = ControllerMode.values()[tagCompound.getInteger("ControllerMode")]; + this.controllerMode = ControllerMode.VALUES[tagCompound.getInteger("ControllerMode")]; + } + + @Override + public void writeInitialSyncData(@NotNull PacketBuffer packetBuffer) { + super.writeInitialSyncData(packetBuffer); + packetBuffer.writeBoolean(isInverted); + packetBuffer.writeShort(controllerMode.ordinal()); + } + + @Override + public void readInitialSyncData(@NotNull PacketBuffer packetBuffer) { + super.readInitialSyncData(packetBuffer); + this.isInverted = packetBuffer.readBoolean(); + this.controllerMode = ControllerMode.VALUES[packetBuffer.readShort()]; } public enum ControllerMode implements IStringSerializable { @@ -254,6 +333,8 @@ public enum ControllerMode implements IStringSerializable { public final String localeName; public final EnumFacing side; + public static final ControllerMode[] VALUES = values(); + ControllerMode(String localeName, EnumFacing side) { this.localeName = localeName; this.side = side; diff --git a/src/main/java/gregtech/common/covers/CoverSolarPanel.java b/src/main/java/gregtech/common/covers/CoverSolarPanel.java index 7e2e750439a..e972ef560ad 100644 --- a/src/main/java/gregtech/common/covers/CoverSolarPanel.java +++ b/src/main/java/gregtech/common/covers/CoverSolarPanel.java @@ -13,7 +13,6 @@ import net.minecraft.util.BlockRenderLayer; import net.minecraft.util.EnumFacing; import net.minecraft.util.ITickable; -import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; @@ -50,10 +49,9 @@ public void renderCover(@NotNull CCRenderState renderState, @NotNull Matrix4 tra public void update() { CoverableView coverable = getCoverableView(); World world = coverable.getWorld(); - BlockPos blockPos = coverable.getPos(); - if (GTUtility.canSeeSunClearly(world, blockPos)) { + if (GTUtility.canSeeSunClearly(world, coverable.getPos())) { IEnergyContainer energyContainer = coverable.getCapability(GregtechCapabilities.CAPABILITY_ENERGY_CONTAINER, - null); + getAttachedSide()); if (energyContainer != null) { energyContainer.acceptEnergyFromNetwork(null, EUt, 1); } diff --git a/src/main/java/gregtech/common/covers/CoverStorage.java b/src/main/java/gregtech/common/covers/CoverStorage.java index a91caa74cb2..a63fe3cbd56 100644 --- a/src/main/java/gregtech/common/covers/CoverStorage.java +++ b/src/main/java/gregtech/common/covers/CoverStorage.java @@ -24,7 +24,7 @@ import codechicken.lib.vec.Matrix4; import com.cleanroommc.modularui.api.drawable.IKey; import com.cleanroommc.modularui.api.widget.IWidget; -import com.cleanroommc.modularui.manager.GuiCreationContext; +import com.cleanroommc.modularui.factory.SidedPosGuiData; import com.cleanroommc.modularui.screen.ModularPanel; import com.cleanroommc.modularui.value.sync.GuiSyncManager; import com.cleanroommc.modularui.value.sync.SyncHandlers; @@ -88,8 +88,7 @@ public boolean usesMui2() { } @Override - public ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManager guiSyncManager, - boolean isClient) { + public ModularPanel buildUI(SidedPosGuiData guiData, GuiSyncManager guiSyncManager) { guiSyncManager.registerSlotGroup("item_inv", this.storageHandler.getSlots()); int rowSize = this.storageHandler.getSlots(); diff --git a/src/main/java/gregtech/common/covers/filter/OreDictionaryItemFilter.java b/src/main/java/gregtech/common/covers/filter/OreDictionaryItemFilter.java index 95cc2f172e0..1db9a6087e3 100644 --- a/src/main/java/gregtech/common/covers/filter/OreDictionaryItemFilter.java +++ b/src/main/java/gregtech/common/covers/filter/OreDictionaryItemFilter.java @@ -2,12 +2,15 @@ import gregtech.api.gui.GuiTextures; import gregtech.api.gui.Widget; +import gregtech.api.gui.resources.TextureArea; import gregtech.api.gui.widgets.DrawableWidget; +import gregtech.api.gui.widgets.ImageCycleButtonWidget; import gregtech.api.gui.widgets.ImageWidget; import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.stack.ItemVariantMap; import gregtech.api.unification.stack.MultiItemVariantMap; import gregtech.api.unification.stack.SingleItemVariantMap; +import gregtech.api.util.function.BooleanConsumer; import gregtech.api.util.oreglob.OreGlob; import gregtech.api.util.oreglob.OreGlobCompileResult; import gregtech.common.covers.filter.oreglob.impl.ImpossibleOreGlob; @@ -18,55 +21,90 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; import net.minecraft.util.text.TextFormatting; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Map; import java.util.Set; +import java.util.function.BooleanSupplier; import java.util.function.Consumer; public class OreDictionaryItemFilter extends ItemFilter { + private final Map> matchCache = new Object2ObjectOpenHashMap<>(); + private final SingleItemVariantMap noOreDictMatch = new SingleItemVariantMap<>(); + protected String expression = ""; + private OreGlob glob = ImpossibleOreGlob.getInstance(); private boolean error; - private final Map> matchCache = new Object2ObjectOpenHashMap<>(); - private final SingleItemVariantMap noOreDictMatch = new SingleItemVariantMap<>(); + private boolean caseSensitive; + /** + * {@code false} requires any of the entry to be match in order for the match to be success, {@code true} requires + * all entries to match + */ + private boolean matchAll; + @NotNull public String getExpression() { return expression; } + @NotNull + public OreGlob getGlob() { + return this.glob; + } + + protected void recompile(@Nullable Consumer<@Nullable OreGlobCompileResult> callback) { + clearCache(); + String expr = this.expression; + if (!expr.isEmpty()) { + OreGlobCompileResult result = OreGlob.compile(expr, !this.caseSensitive); + this.glob = result.getInstance(); + this.error = result.hasError(); + if (callback != null) callback.accept(result); + } else { + this.glob = ImpossibleOreGlob.getInstance(); + this.error = true; + if (callback != null) callback.accept(null); + } + } + + protected void clearCache() { + this.matchCache.clear(); + this.noOreDictMatch.clear(); + } + @Override public void initUI(Consumer widgetGroup) { ItemOreFilterTestSlot[] testSlot = new ItemOreFilterTestSlot[5]; for (int i = 0; i < testSlot.length; i++) { - testSlot[i] = new ItemOreFilterTestSlot(20 + 22 * i, 0); - widgetGroup.accept(testSlot[i]); + ItemOreFilterTestSlot slot = new ItemOreFilterTestSlot(20 + 22 * i, 0); + slot.setGlob(getGlob()); + slot.setMatchAll(this.matchAll); + widgetGroup.accept(slot); + testSlot[i] = slot; } OreGlobCompileStatusWidget compilationStatus = new OreGlobCompileStatusWidget(10, 10); + + Consumer<@Nullable OreGlobCompileResult> compileCallback = result -> { + compilationStatus.setCompileResult(result); + for (ItemOreFilterTestSlot slot : testSlot) { + slot.setGlob(getGlob()); + } + }; + HighlightedTextField textField = new HighlightedTextField(14, 26, 152, 14, () -> this.expression, s -> { if (s.equals(this.expression)) return; this.expression = s; - if (!s.isEmpty()) { - OreGlobCompileResult result = OreGlob.compile(s); - this.glob = result.getInstance(); - this.error = result.hasError(); - compilationStatus.setCompileResult(result); - } else { - this.glob = ImpossibleOreGlob.getInstance(); - this.error = true; - compilationStatus.setCompileResult(null); - } - this.matchCache.clear(); - this.noOreDictMatch.clear(); markDirty(); - for (ItemOreFilterTestSlot slot : testSlot) { - slot.setGlob(this.error ? null : this.glob); - } + recompile(compileCallback); }); compilationStatus.setTextField(textField); @@ -91,7 +129,7 @@ public void initUI(Consumer widgetGroup) { case '*', '?' -> h.format(i, TextFormatting.GREEN); case '!' -> h.format(i, TextFormatting.RED); case '\\' -> h.format(i++, TextFormatting.YELLOW); - case '$' -> { + case '$' -> { // TODO: remove this switch case in 2.9 h.format(i, TextFormatting.DARK_GREEN); for (; i < t.length(); i++) { switch (t.charAt(i)) { @@ -114,6 +152,25 @@ public void initUI(Consumer widgetGroup) { h.format(i + 1, TextFormatting.RESET); } }).setMaxLength(64)); + widgetGroup.accept(new ForcedInitialSyncImageCycleButtonWidget(130, 38, 18, 18, + GuiTextures.ORE_FILTER_BUTTON_CASE_SENSITIVE, () -> this.caseSensitive, caseSensitive -> { + if (this.caseSensitive == caseSensitive) return; + this.caseSensitive = caseSensitive; + markDirty(); + recompile(compileCallback); + }).setTooltipHoverString( + i -> "cover.ore_dictionary_filter.button.case_sensitive." + (i == 0 ? "disabled" : "enabled"))); + widgetGroup.accept(new ForcedInitialSyncImageCycleButtonWidget(148, 38, 18, 18, + GuiTextures.ORE_FILTER_BUTTON_MATCH_ALL, () -> this.matchAll, matchAll -> { + if (this.matchAll == matchAll) return; + this.matchAll = matchAll; + markDirty(); + clearCache(); + for (ItemOreFilterTestSlot slot : testSlot) { + slot.setMatchAll(matchAll); + } + }).setTooltipHoverString( + i -> "cover.ore_dictionary_filter.button.match_all." + (i == 0 ? "disabled" : "enabled"))); } @Override @@ -122,7 +179,7 @@ public Object matchItemStack(ItemStack itemStack) { "wtf is this system?? i can put any non null object here and it i will work??? $arch" : null; } - public boolean matchesItemStack(ItemStack itemStack) { + public boolean matchesItemStack(@NotNull ItemStack itemStack) { if (this.error) return false; Item item = itemStack.getItem(); ItemVariantMap> oreDictEntry = OreDictUnifier.getOreDictionaryEntry(item); @@ -160,7 +217,7 @@ public boolean matchesItemStack(ItemStack itemStack) { } this.matchCache.put(item, cacheEntry); } - boolean matches = this.glob.matches(itemStack); + boolean matches = this.matchAll ? this.glob.matchesAll(itemStack) : this.glob.matchesAny(itemStack); cacheEntry.put(itemStack, matches); return matches; } @@ -181,20 +238,43 @@ public int getTotalOccupiedHeight() { } @Override - public void writeToNBT(NBTTagCompound tagCompound) { - tagCompound.setString("OreDictionaryFilter", expression); + public void writeToNBT(NBTTagCompound tag) { + tag.setString("OreDictionaryFilter", expression); + if (this.caseSensitive) tag.setBoolean("caseSensitive", true); + if (this.matchAll) tag.setBoolean("matchAll", true); } @Override - public void readFromNBT(NBTTagCompound tagCompound) { - this.expression = tagCompound.getString("OreDictionaryFilter"); - if (!this.expression.isEmpty()) { - OreGlobCompileResult result = OreGlob.compile(this.expression); - this.glob = result.getInstance(); - this.error = result.hasError(); - } else { - this.glob = ImpossibleOreGlob.getInstance(); - this.error = true; + public void readFromNBT(NBTTagCompound tag) { + this.expression = tag.getString("OreDictionaryFilter"); + this.caseSensitive = tag.getBoolean("caseSensitive"); + this.matchAll = tag.getBoolean("matchAll"); + recompile(null); + } + + public static class ForcedInitialSyncImageCycleButtonWidget extends ImageCycleButtonWidget { + + private final BooleanConsumer updater; + + public ForcedInitialSyncImageCycleButtonWidget(int xPosition, int yPosition, int width, int height, + TextureArea buttonTexture, BooleanSupplier supplier, + BooleanConsumer updater) { + super(xPosition, yPosition, width, height, buttonTexture, supplier, updater); + this.currentOption = 0; + this.updater = updater; + } + + @Override + public void readUpdateInfo(int id, PacketBuffer buffer) { + if (id == 1) { + int currentOptionCache = this.currentOption; + super.readUpdateInfo(id, buffer); + if (this.currentOption != currentOptionCache) { + this.updater.apply(currentOption >= 1); // call updater to apply necessary state changes + } + } else { + super.readUpdateInfo(id, buffer); + } } } } diff --git a/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobMessages.java b/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobMessages.java index 6b3da3ca6fd..d0c2d44e7df 100644 --- a/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobMessages.java +++ b/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobMessages.java @@ -2,6 +2,8 @@ import gregtech.api.util.LocalizationUtils; +import org.jetbrains.annotations.ApiStatus; + import java.util.Locale; interface OreGlobMessages { @@ -102,21 +104,32 @@ static String compileErrorUnexpectedTokenAfterEOF(String token) { return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "unexpected_token_after_eof", token); } + // compilation flags are expected to be removed in future release + + @Deprecated + @SuppressWarnings("DeprecatedIsStillUsed") + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") static String compileErrorUnexpectedCompilationFlag() { // Compilation flags in the middle of expression return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "unexpected_compilation_flag"); } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") static String compileErrorEmptyCompilationFlag() { // No compilation flags given return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "empty_compilation_flag"); } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") static String compileErrorUnknownCompilationFlag(String flag) { // Unknown compilation flag '%s' return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "unknown_compilation_flag", flag); } + @Deprecated + @ApiStatus.ScheduledForRemoval(inVersion = "2.9") static String compileErrorRedundantCompilationFlag(String flag) { // Compilation flag '%s' written twice return LocalizationUtils.format(COMPILE_ERROR_PREFIX + "redundant_compilation_flag", flag); diff --git a/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobParser.java b/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobParser.java index 0bc564d69db..b4cec035040 100644 --- a/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobParser.java +++ b/src/main/java/gregtech/common/covers/filter/oreglob/impl/OreGlobParser.java @@ -5,6 +5,7 @@ import gregtech.common.covers.filter.oreglob.node.OreGlobNode; import gregtech.common.covers.filter.oreglob.node.OreGlobNodes; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -14,7 +15,7 @@ /** * Top-down parser for oreGlob expression. - * + * *

  * oreGlob = [ FLAG ], [ or ], EOF
  *
@@ -47,8 +48,7 @@ public final class OreGlobParser {
 
     private boolean error;
 
-    private boolean caseSensitive;
-
+    private boolean ignoreCase;
     private int inputIndex;
 
     private TokenType tokenType;
@@ -56,8 +56,9 @@ public final class OreGlobParser {
     @Nullable
     private String tokenLiteralValue;
 
-    public OreGlobParser(String input) {
+    public OreGlobParser(String input, boolean ignoreCase) {
         this.input = input;
+        this.ignoreCase = ignoreCase;
     }
 
     // Get codepoint at current position and incr index
@@ -68,6 +69,7 @@ private int readNextChar() {
         return input.codePointAt(i);
     }
 
+    @SuppressWarnings("deprecation")
     private void advance() {
         boolean first = this.inputIndex == 0;
         while (true) {
@@ -84,7 +86,7 @@ private void advance() {
                 case '^' -> setCurrentToken(XOR, start, 1);
                 case '*' -> setCurrentToken(ANY, start, 1);
                 case '?' -> setCurrentToken(ANY_CHAR, start, 1);
-                case '$' -> {
+                case '$' -> { // TODO: remove this switch case in 2.9
                     if (!first) {
                         error(OreGlobMessages.compileErrorUnexpectedCompilationFlag(), start, 1);
                     }
@@ -145,8 +147,10 @@ private String gatherLiteralValue() {
         }
     }
 
+    @Deprecated
+    @SuppressWarnings("DeprecatedIsStillUsed")
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
     private void gatherFlags(boolean add) {
-        boolean flagsAdded = false;
         while (true) {
             int i = this.inputIndex;
             int c = readNextChar();
@@ -156,39 +160,27 @@ private void gatherFlags(boolean add) {
                     if (c == CHAR_EOF) {
                         error(OreGlobMessages.compileErrorEOFAfterEscape(), i, 1);
                     } else if (add) {
-                        addFlag(c, i);
-                        flagsAdded = true;
+                        addFlag(c);
                         continue;
                     }
                 }
                 case ' ', '\t', '\n', '\r', CHAR_EOF -> {}
                 default -> {
                     if (add) {
-                        addFlag(c, i);
-                        flagsAdded = true;
+                        addFlag(c);
                     }
                     continue;
                 }
             }
-            if (!flagsAdded && add) {
-                error(OreGlobMessages.compileErrorEmptyCompilationFlag(), i, 1);
-            }
+            warn("Compilation flags ('$') are scheduled to be removed in future releases.");
             return;
         }
     }
 
-    private void addFlag(int flag, int index) {
-        switch (flag) {
-            case 'c', 'C' -> {
-                if (this.caseSensitive) {
-                    warn(OreGlobMessages.compileErrorRedundantCompilationFlag("c"), index, 1);
-                } else {
-                    this.caseSensitive = true;
-                }
-            }
-            default -> warn(OreGlobMessages.compileErrorUnknownCompilationFlag(
-                    new StringBuilder().appendCodePoint(flag).toString()), index, 1);
-        }
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
+    private void addFlag(int flag) {
+        if (flag == 'c' || flag == 'C') this.ignoreCase = false;
     }
 
     private boolean advanceIf(TokenType type) {
@@ -296,7 +288,7 @@ private OreGlobNode primary() {
         return switch (tokenType) {
             case LITERAL -> {
                 if (tokenLiteralValue != null) {
-                    OreGlobNode result = OreGlobNodes.match(tokenLiteralValue, !this.caseSensitive);
+                    OreGlobNode result = OreGlobNodes.match(tokenLiteralValue, this.ignoreCase);
                     advance();
                     yield result;
                 } else { // likely caused by program error, not user issue
diff --git a/src/main/java/gregtech/common/creativetab/GTCreativeTabs.java b/src/main/java/gregtech/common/creativetab/GTCreativeTabs.java
new file mode 100644
index 00000000000..95d308dde3e
--- /dev/null
+++ b/src/main/java/gregtech/common/creativetab/GTCreativeTabs.java
@@ -0,0 +1,34 @@
+package gregtech.common.creativetab;
+
+import gregtech.api.GTValues;
+import gregtech.api.creativetab.BaseCreativeTab;
+import gregtech.api.unification.OreDictUnifier;
+import gregtech.api.unification.material.Materials;
+import gregtech.api.unification.ore.OrePrefix;
+import gregtech.common.blocks.BlockWarningSign;
+import gregtech.common.blocks.MetaBlocks;
+import gregtech.common.items.MetaItems;
+import gregtech.common.items.ToolItems;
+import gregtech.common.metatileentities.MetaTileEntities;
+
+public final class GTCreativeTabs {
+
+    public static final BaseCreativeTab TAB_GREGTECH = new BaseCreativeTab(GTValues.MODID + ".main",
+            () -> MetaItems.LOGO.getStackForm(), true);
+    public static final BaseCreativeTab TAB_GREGTECH_MACHINES = new BaseCreativeTab(GTValues.MODID + ".machines",
+            () -> MetaTileEntities.ELECTRIC_BLAST_FURNACE.getStackForm(), true);
+    public static final BaseCreativeTab TAB_GREGTECH_CABLES = new BaseCreativeTab(GTValues.MODID + ".cables",
+            () -> OreDictUnifier.get(OrePrefix.cableGtDouble, Materials.Aluminium), true);
+    public static final BaseCreativeTab TAB_GREGTECH_PIPES = new BaseCreativeTab(GTValues.MODID + ".pipes",
+            () -> OreDictUnifier.get(OrePrefix.pipeNormalFluid, Materials.Aluminium), true);
+    public static final BaseCreativeTab TAB_GREGTECH_TOOLS = new BaseCreativeTab(GTValues.MODID + ".tools",
+            () -> ToolItems.HARD_HAMMER.get(Materials.Aluminium), true);
+    public static final BaseCreativeTab TAB_GREGTECH_MATERIALS = new BaseCreativeTab(GTValues.MODID + ".materials",
+            () -> OreDictUnifier.get(OrePrefix.ingot, Materials.Aluminium), true);
+    public static final BaseCreativeTab TAB_GREGTECH_ORES = new BaseCreativeTab(GTValues.MODID + ".ores",
+            () -> OreDictUnifier.get(OrePrefix.ore, Materials.Aluminium), true);
+    public static final BaseCreativeTab TAB_GREGTECH_DECORATIONS = new BaseCreativeTab(GTValues.MODID + ".decorations",
+            () -> MetaBlocks.WARNING_SIGN.getItemVariant(BlockWarningSign.SignType.YELLOW_STRIPES), true);
+
+    private GTCreativeTabs() {}
+}
diff --git a/src/main/java/gregtech/common/entities/EntityGTExplosive.java b/src/main/java/gregtech/common/entities/EntityGTExplosive.java
new file mode 100644
index 00000000000..70233d7f17b
--- /dev/null
+++ b/src/main/java/gregtech/common/entities/EntityGTExplosive.java
@@ -0,0 +1,136 @@
+package gregtech.common.entities;
+
+import gregtech.api.util.BlockUtility;
+import gregtech.api.util.GregFakePlayer;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.material.Material;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.entity.MoverType;
+import net.minecraft.entity.item.EntityItem;
+import net.minecraft.entity.item.EntityTNTPrimed;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.EnumParticleTypes;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraft.world.WorldServer;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+public abstract class EntityGTExplosive extends EntityTNTPrimed {
+
+    public EntityGTExplosive(World world, double x, double y, double z, EntityLivingBase exploder) {
+        super(world, x, y, z, exploder);
+    }
+
+    @SuppressWarnings("unused")
+    public EntityGTExplosive(World world) {
+        super(world);
+    }
+
+    /**
+     * @return The strength of the explosive.
+     */
+    protected abstract float getStrength();
+
+    /**
+     * @return Whether to drop all blocks, or use default logic
+     */
+    public abstract boolean dropsAllBlocks();
+
+    /**
+     * @return The range of the explosive, if {@link #dropsAllBlocks} is true.
+     */
+    protected int getRange() {
+        return 2;
+    }
+
+    /**
+     * @return The block state of the block this explosion entity is created by.
+     */
+    public abstract @NotNull IBlockState getExplosiveState();
+
+    @Override
+    public void onUpdate() {
+        this.prevPosX = this.posX;
+        this.prevPosY = this.posY;
+        this.prevPosZ = this.posZ;
+        if (!this.hasNoGravity()) {
+            this.motionY -= 0.03999999910593033D;
+        }
+
+        this.move(MoverType.SELF, this.motionX, this.motionY, this.motionZ);
+        this.motionX *= 0.9800000190734863D;
+        this.motionY *= 0.9800000190734863D;
+        this.motionZ *= 0.9800000190734863D;
+        if (this.onGround) {
+            this.motionX *= 0.699999988079071D;
+            this.motionZ *= 0.699999988079071D;
+            this.motionY *= -0.5D;
+        }
+
+        setFuse(this.getFuse() - 1);
+        if (this.getFuse() <= 0) {
+            this.setDead();
+            if (!this.world.isRemote) {
+                this.explodeTNT();
+            }
+        } else {
+            this.handleWaterMovement();
+            this.world.spawnParticle(EnumParticleTypes.SMOKE_NORMAL, this.posX, this.posY + 0.5D, this.posZ, 0.0D, 0.0D,
+                    0.0D);
+        }
+    }
+
+    protected void explodeTNT() {
+        this.world.createExplosion(this, this.posX, this.posY + (double) (this.height / 16.0F), this.posZ,
+                getStrength(), !dropsAllBlocks());
+
+        // If we don't drop all blocks, then skip the drop capture logic
+        if (!dropsAllBlocks()) return;
+
+        // Create the fake explosion but don't destroy any blocks in water, per MC behavior
+        if (this.inWater) return;
+
+        EntityPlayer player = GregFakePlayer.get((WorldServer) world);
+
+        int range = getRange();
+        for (BlockPos pos : BlockPos.getAllInBox(this.getPosition().add(-range, -range, -range),
+                this.getPosition().add(range, range, range))) {
+            IBlockState state = world.getBlockState(pos);
+
+            if (state.getMaterial() == Material.AIR) continue;
+            if (state.getMaterial() == Material.WATER || state.getMaterial() == Material.LAVA) continue;
+
+            float hardness = state.getBlockHardness(world, pos);
+            float resistance = state.getBlock().getExplosionResistance(player);
+
+            if (hardness >= 0.0f && resistance < 100 && world.isBlockModifiable(player, pos)) {
+                List drops = attemptBreakBlockAndObtainDrops(pos, state, player);
+
+                for (ItemStack stack : drops) {
+                    EntityItem entity = new EntityItem(world, pos.getX(), pos.getY(), pos.getZ(), stack);
+                    entity.setDefaultPickupDelay();
+                    world.spawnEntity(entity);
+                }
+            }
+        }
+    }
+
+    private List attemptBreakBlockAndObtainDrops(BlockPos pos, IBlockState state, EntityPlayer player) {
+        if (state.getBlock().removedByPlayer(state, world, pos, player, true)) {
+            world.playEvent(null, 2001, pos, Block.getStateId(state));
+            state.getBlock().onPlayerDestroy(world, pos, state);
+
+            BlockUtility.startCaptureDrops();
+            state.getBlock().harvestBlock(world, player, pos, state, world.getTileEntity(pos), ItemStack.EMPTY);
+            return BlockUtility.stopCaptureDrops();
+        }
+        return Collections.emptyList();
+    }
+}
diff --git a/src/main/java/gregtech/common/entities/ITNTEntity.java b/src/main/java/gregtech/common/entities/ITNTEntity.java
new file mode 100644
index 00000000000..3fa60cf4860
--- /dev/null
+++ b/src/main/java/gregtech/common/entities/ITNTEntity.java
@@ -0,0 +1,41 @@
+package gregtech.common.entities;
+
+import gregtech.common.blocks.MetaBlocks;
+
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.world.World;
+
+import org.jetbrains.annotations.NotNull;
+
+public class ITNTEntity extends EntityGTExplosive {
+
+    public ITNTEntity(World world, double x, double y, double z, EntityLivingBase exploder) {
+        super(world, x, y, z, exploder);
+    }
+
+    @SuppressWarnings("unused")
+    public ITNTEntity(World world) {
+        super(world);
+    }
+
+    @Override
+    protected float getStrength() {
+        return 5.0F;
+    }
+
+    @Override
+    public boolean dropsAllBlocks() {
+        return true;
+    }
+
+    @Override
+    protected int getRange() {
+        return 3;
+    }
+
+    @Override
+    public @NotNull IBlockState getExplosiveState() {
+        return MetaBlocks.ITNT.getDefaultState();
+    }
+}
diff --git a/src/main/java/gregtech/common/entities/PowderbarrelEntity.java b/src/main/java/gregtech/common/entities/PowderbarrelEntity.java
new file mode 100644
index 00000000000..39f1ed4df56
--- /dev/null
+++ b/src/main/java/gregtech/common/entities/PowderbarrelEntity.java
@@ -0,0 +1,36 @@
+package gregtech.common.entities;
+
+import gregtech.common.blocks.MetaBlocks;
+
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.world.World;
+
+import org.jetbrains.annotations.NotNull;
+
+public class PowderbarrelEntity extends EntityGTExplosive {
+
+    public PowderbarrelEntity(World world, double x, double y, double z, EntityLivingBase exploder) {
+        super(world, x, y, z, exploder);
+    }
+
+    @SuppressWarnings("unused")
+    public PowderbarrelEntity(World world) {
+        super(world);
+    }
+
+    @Override
+    protected float getStrength() {
+        return 3.5F;
+    }
+
+    @Override
+    public boolean dropsAllBlocks() {
+        return true;
+    }
+
+    @Override
+    public @NotNull IBlockState getExplosiveState() {
+        return MetaBlocks.POWDERBARREL.getDefaultState();
+    }
+}
diff --git a/src/main/java/gregtech/common/gui/widget/appeng/AEConfigWidget.java b/src/main/java/gregtech/common/gui/widget/appeng/AEConfigWidget.java
index 59005459893..1195fa8f34f 100644
--- a/src/main/java/gregtech/common/gui/widget/appeng/AEConfigWidget.java
+++ b/src/main/java/gregtech/common/gui/widget/appeng/AEConfigWidget.java
@@ -6,7 +6,7 @@
 import gregtech.api.util.Size;
 import gregtech.common.gui.widget.appeng.slot.AEConfigSlot;
 import gregtech.common.gui.widget.appeng.slot.AmountSetSlot;
-import gregtech.common.metatileentities.multi.multiblockpart.appeng.IConfigurableSlot;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.IConfigurableSlot;
 
 import appeng.api.storage.data.IAEStack;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -26,26 +26,34 @@ public abstract class AEConfigWidget> extends AbstractWidg
     protected Int2ObjectMap> changeMap = new Int2ObjectOpenHashMap<>();
     protected IConfigurableSlot[] displayList;
     protected AmountSetSlot amountSetWidget;
+    protected final boolean isStocking;
     protected final static int UPDATE_ID = 1000;
 
-    public AEConfigWidget(int x, int y, IConfigurableSlot[] config) {
+    public AEConfigWidget(int x, int y, IConfigurableSlot[] config, boolean isStocking) {
         super(new Position(x, y), new Size(config.length * 18, 18 * 2));
+        this.isStocking = isStocking;
         this.config = config;
         this.init();
-        this.amountSetWidget = new AmountSetSlot<>(80, -40, this);
-        this.addWidget(this.amountSetWidget);
-        this.addWidget(this.amountSetWidget.getText());
-        this.amountSetWidget.setVisible(false);
-        this.amountSetWidget.getText().setVisible(false);
+        if (!isStocking()) {
+            this.amountSetWidget = new AmountSetSlot<>(80, -40, this);
+            this.addWidget(this.amountSetWidget);
+            this.addWidget(this.amountSetWidget.getText());
+            this.amountSetWidget.setVisible(false);
+            this.amountSetWidget.getText().setVisible(false);
+        }
     }
 
     public void enableAmount(int slotIndex) {
+        // Only allow the amount set widget if not stocking, as amount is useless for stocking
+        if (isStocking()) return;
         this.amountSetWidget.setSlotIndex(slotIndex);
         this.amountSetWidget.setVisible(true);
         this.amountSetWidget.getText().setVisible(true);
     }
 
     public void disableAmount() {
+        // Only allow the amount set widget if not stocking, as amount is useless for stocking
+        if (isStocking()) return;
         this.amountSetWidget.setSlotIndex(-1);
         this.amountSetWidget.setVisible(false);
         this.amountSetWidget.getText().setVisible(false);
@@ -53,22 +61,29 @@ public void disableAmount() {
 
     @Override
     public boolean mouseClicked(int mouseX, int mouseY, int button) {
-        if (this.amountSetWidget.isVisible()) {
-            if (this.amountSetWidget.getText().mouseClicked(mouseX, mouseY, button)) {
-                return true;
+        // Only allow the amount set widget if not stocking, as amount is useless for stocking
+        if (!isStocking()) {
+            if (this.amountSetWidget.isVisible()) {
+                if (this.amountSetWidget.getText().mouseClicked(mouseX, mouseY, button)) {
+                    return true;
+                }
             }
-        }
-        for (Widget w : this.widgets) {
-            if (w instanceof AEConfigSlot) {
-                ((AEConfigSlot) w).setSelect(false);
+            for (Widget w : this.widgets) {
+                if (w instanceof AEConfigSlot) {
+                    ((AEConfigSlot) w).setSelect(false);
+                }
             }
+            this.disableAmount();
         }
-        this.disableAmount();
         return super.mouseClicked(mouseX, mouseY, button);
     }
 
     abstract void init();
 
+    public boolean isStocking() {
+        return isStocking;
+    }
+
     @Override
     public void detectAndSendChanges() {
         super.detectAndSendChanges();
diff --git a/src/main/java/gregtech/common/gui/widget/appeng/AEFluidConfigWidget.java b/src/main/java/gregtech/common/gui/widget/appeng/AEFluidConfigWidget.java
index 1599217290c..b5f709b0b86 100644
--- a/src/main/java/gregtech/common/gui/widget/appeng/AEFluidConfigWidget.java
+++ b/src/main/java/gregtech/common/gui/widget/appeng/AEFluidConfigWidget.java
@@ -1,39 +1,49 @@
 package gregtech.common.gui.widget.appeng;
 
 import gregtech.common.gui.widget.appeng.slot.AEFluidConfigSlot;
-import gregtech.common.metatileentities.multi.multiblockpart.appeng.IConfigurableSlot;
-import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEInputHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEFluidList;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEFluidSlot;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.IConfigurableSlot;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedFluidStack;
 
 import net.minecraft.network.PacketBuffer;
+import net.minecraftforge.fluids.FluidStack;
 
 import appeng.api.storage.data.IAEFluidStack;
 
-/**
- * @Author GlodBlock
- * @Description Display {@link IAEFluidStack} config
- * @Date 2023/4/21-1:45
- */
 public class AEFluidConfigWidget extends AEConfigWidget {
 
-    public AEFluidConfigWidget(int x, int y, IConfigurableSlot[] config) {
-        super(x, y, config);
+    final ExportOnlyAEFluidList fluidList;
+
+    public AEFluidConfigWidget(int x, int y, ExportOnlyAEFluidList fluidList) {
+        super(x, y, fluidList.getInventory(), fluidList.isStocking());
+        this.fluidList = fluidList;
     }
 
     @Override
     @SuppressWarnings("unchecked")
     void init() {
-        int line;
+        final int size = (int) Math.sqrt(this.config.length);
         this.displayList = new IConfigurableSlot[this.config.length];
         this.cached = new IConfigurableSlot[this.config.length];
-        for (int index = 0; index < this.config.length; index++) {
-            this.displayList[index] = new MetaTileEntityMEInputHatch.ExportOnlyAEFluid();
-            this.cached[index] = new MetaTileEntityMEInputHatch.ExportOnlyAEFluid();
-            line = index / 8;
-            this.addWidget(new AEFluidConfigSlot((index - line * 8) * 18, line * (18 * 2 + 2), this, index));
+        for (int h = 0; h < size; h++) {
+            for (int w = 0; w < size; w++) {
+                final int index = h * size + w;
+                this.displayList[index] = new ExportOnlyAEFluidSlot();
+                this.cached[index] = new ExportOnlyAEFluidSlot();
+                this.addWidget(new AEFluidConfigSlot(w * 18, h * 18, this, index));
+            }
         }
     }
 
+    public boolean hasStackInConfig(FluidStack stack) {
+        return fluidList.hasStackInConfig(stack, true);
+    }
+
+    public boolean isAutoPull() {
+        return fluidList.isAutoPull();
+    }
+
     @Override
     public void readUpdateInfo(int id, PacketBuffer buffer) {
         super.readUpdateInfo(id, buffer);
diff --git a/src/main/java/gregtech/common/gui/widget/appeng/AEItemConfigWidget.java b/src/main/java/gregtech/common/gui/widget/appeng/AEItemConfigWidget.java
index c3707d4afaf..0f2afe98235 100644
--- a/src/main/java/gregtech/common/gui/widget/appeng/AEItemConfigWidget.java
+++ b/src/main/java/gregtech/common/gui/widget/appeng/AEItemConfigWidget.java
@@ -1,39 +1,49 @@
 package gregtech.common.gui.widget.appeng;
 
 import gregtech.common.gui.widget.appeng.slot.AEItemConfigSlot;
-import gregtech.common.metatileentities.multi.multiblockpart.appeng.IConfigurableSlot;
-import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEInputBus;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEItemList;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEItemSlot;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.IConfigurableSlot;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedItemStack;
 
+import net.minecraft.item.ItemStack;
 import net.minecraft.network.PacketBuffer;
 
 import appeng.api.storage.data.IAEItemStack;
 
-/**
- * @Author GlodBlock
- * @Description Display {@link IAEItemStack} config
- * @Date 2023/4/22-1:02
- */
 public class AEItemConfigWidget extends AEConfigWidget {
 
-    public AEItemConfigWidget(int x, int y, IConfigurableSlot[] config) {
-        super(x, y, config);
+    final ExportOnlyAEItemList itemList;
+
+    public AEItemConfigWidget(int x, int y, ExportOnlyAEItemList itemList) {
+        super(x, y, itemList.getInventory(), itemList.isStocking());
+        this.itemList = itemList;
     }
 
     @Override
     @SuppressWarnings("unchecked")
     void init() {
-        int line;
+        final int size = (int) Math.sqrt(this.config.length);
         this.displayList = new IConfigurableSlot[this.config.length];
         this.cached = new IConfigurableSlot[this.config.length];
-        for (int index = 0; index < this.config.length; index++) {
-            this.displayList[index] = new MetaTileEntityMEInputBus.ExportOnlyAEItem();
-            this.cached[index] = new MetaTileEntityMEInputBus.ExportOnlyAEItem();
-            line = index / 8;
-            this.addWidget(new AEItemConfigSlot((index - line * 8) * 18, line * (18 * 2 + 2), this, index));
+        for (int h = 0; h < size; h++) {
+            for (int w = 0; w < size; w++) {
+                final int index = h * size + w;
+                this.displayList[index] = new ExportOnlyAEItemSlot();
+                this.cached[index] = new ExportOnlyAEItemSlot();
+                this.addWidget(new AEItemConfigSlot(w * 18, h * 18, this, index));
+            }
         }
     }
 
+    public boolean hasStackInConfig(ItemStack stack) {
+        return itemList.hasStackInConfig(stack, true);
+    }
+
+    public boolean isAutoPull() {
+        return itemList.isAutoPull();
+    }
+
     @Override
     public void readUpdateInfo(int id, PacketBuffer buffer) {
         super.readUpdateInfo(id, buffer);
diff --git a/src/main/java/gregtech/common/gui/widget/appeng/slot/AEConfigSlot.java b/src/main/java/gregtech/common/gui/widget/appeng/slot/AEConfigSlot.java
index ed1b9145bd7..d663340a451 100644
--- a/src/main/java/gregtech/common/gui/widget/appeng/slot/AEConfigSlot.java
+++ b/src/main/java/gregtech/common/gui/widget/appeng/slot/AEConfigSlot.java
@@ -5,7 +5,7 @@
 import gregtech.api.util.Position;
 import gregtech.api.util.Size;
 import gregtech.common.gui.widget.appeng.AEConfigWidget;
-import gregtech.common.metatileentities.multi.multiblockpart.appeng.IConfigurableSlot;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.IConfigurableSlot;
 
 import net.minecraft.client.resources.I18n;
 import net.minecraft.item.ItemStack;
@@ -17,13 +17,9 @@
 import java.util.Collections;
 import java.util.List;
 
-/**
- * @Author GlodBlock
- * @Description A configurable slot
- * @Date 2023/4/22-0:30
- */
 public class AEConfigSlot> extends Widget implements IGhostIngredientTarget {
 
+    protected static final int DISPLAY_X_OFFSET = 18 * 5;
     protected AEConfigWidget parentWidget;
     protected int index;
     protected final static int REMOVE_ID = 1000;
@@ -43,14 +39,24 @@ public void drawInForeground(int mouseX, int mouseY) {
         IConfigurableSlot slot = this.parentWidget.getDisplay(this.index);
         if (slot.getConfig() == null && mouseOverConfig(mouseX, mouseY)) {
             List hoverStringList = new ArrayList<>();
-            hoverStringList.add(I18n.format("gregtech.gui.config_slot"));
-            hoverStringList.add(I18n.format("gregtech.gui.config_slot.set"));
-            hoverStringList.add(I18n.format("gregtech.gui.config_slot.scroll"));
-            hoverStringList.add(I18n.format("gregtech.gui.config_slot.remove"));
-            drawHoveringText(ItemStack.EMPTY, hoverStringList, -1, mouseX, mouseY);
+            addHoverText(hoverStringList);
+            if (!hoverStringList.isEmpty()) {
+                drawHoveringText(ItemStack.EMPTY, hoverStringList, -1, mouseX, mouseY);
+            }
         }
     }
 
+    protected void addHoverText(List hoverText) {
+        hoverText.add(I18n.format("gregtech.gui.config_slot"));
+        if (!parentWidget.isStocking()) {
+            hoverText.add(I18n.format("gregtech.gui.config_slot.set"));
+            hoverText.add(I18n.format("gregtech.gui.config_slot.scroll"));
+        } else {
+            hoverText.add(I18n.format("gregtech.gui.config_slot.set_only"));
+        }
+        hoverText.add(I18n.format("gregtech.gui.config_slot.remove"));
+    }
+
     public void setSelect(boolean val) {
         this.select = val;
     }
@@ -62,11 +68,15 @@ protected boolean mouseOverConfig(int mouseX, int mouseY) {
 
     protected boolean mouseOverStock(int mouseX, int mouseY) {
         Position position = getPosition();
-        return isMouseOver(position.x, position.y + 18, 18, 18, mouseX, mouseY);
+        return isMouseOver(position.x + DISPLAY_X_OFFSET, position.y, 18, 18, mouseX, mouseY);
     }
 
     @Override
     public List> getPhantomTargets(Object ingredient) {
         return Collections.emptyList();
     }
+
+    public AEConfigWidget getParentWidget() {
+        return parentWidget;
+    }
 }
diff --git a/src/main/java/gregtech/common/gui/widget/appeng/slot/AEFluidConfigSlot.java b/src/main/java/gregtech/common/gui/widget/appeng/slot/AEFluidConfigSlot.java
index b4ab7e87beb..2b852fdeb14 100644
--- a/src/main/java/gregtech/common/gui/widget/appeng/slot/AEFluidConfigSlot.java
+++ b/src/main/java/gregtech/common/gui/widget/appeng/slot/AEFluidConfigSlot.java
@@ -7,10 +7,11 @@
 import gregtech.api.util.Size;
 import gregtech.api.util.TextFormattingUtil;
 import gregtech.client.utils.RenderUtil;
-import gregtech.common.gui.widget.appeng.AEConfigWidget;
-import gregtech.common.metatileentities.multi.multiblockpart.appeng.IConfigurableSlot;
+import gregtech.common.gui.widget.appeng.AEFluidConfigWidget;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.IConfigurableSlot;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedFluidStack;
 
+import net.minecraft.client.resources.I18n;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.network.PacketBuffer;
@@ -34,27 +35,26 @@
 import static gregtech.api.capability.GregtechDataCodes.LOAD_PHANTOM_FLUID_STACK_FROM_NBT;
 import static gregtech.api.util.GTUtility.getFluidFromContainer;
 
-/**
- * @Author GlodBlock
- * @Description A configurable slot for {@link IAEFluidStack}
- * @Date 2023/4/21-0:50
- */
 public class AEFluidConfigSlot extends AEConfigSlot {
 
-    public AEFluidConfigSlot(int x, int y, AEConfigWidget widget, int index) {
-        super(new Position(x, y), new Size(18, 18 * 2), widget, index);
+    public AEFluidConfigSlot(int x, int y, AEFluidConfigWidget widget, int index) {
+        super(new Position(x, y), new Size(18 * 6, 18), widget, index);
+    }
+
+    @Override
+    public AEFluidConfigWidget getParentWidget() {
+        return (AEFluidConfigWidget) super.getParentWidget();
     }
 
     @Override
     public void drawInBackground(int mouseX, int mouseY, float partialTicks, IRenderContext context) {
         super.drawInBackground(mouseX, mouseY, partialTicks, context);
+        AEFluidConfigWidget pw = getParentWidget();
         Position position = getPosition();
-        IConfigurableSlot slot = this.parentWidget.getDisplay(this.index);
+        IConfigurableSlot slot = pw.getDisplay(this.index);
         IAEFluidStack config = slot.getConfig();
         IAEFluidStack stock = slot.getStock();
-        GuiTextures.FLUID_SLOT.draw(position.x, position.y, 18, 18);
-        GuiTextures.FLUID_SLOT.draw(position.x, position.y + 18, 18, 18);
-        GuiTextures.CONFIG_ARROW.draw(position.x, position.y, 18, 18);
+        drawSlots(pw.isAutoPull(), position.x, position.y);
         if (this.select) {
             GuiTextures.SELECT_BOX.draw(position.x, position.y, 18, 18);
         }
@@ -62,22 +62,35 @@ public void drawInBackground(int mouseX, int mouseY, float partialTicks, IRender
         int stackY = position.y + 1;
         if (config != null) {
             RenderUtil.drawFluidForGui(config.getFluidStack(), config.getFluidStack().amount, stackX, stackY, 17, 17);
-            String amountStr = TextFormattingUtil.formatLongToCompactString(config.getStackSize(), 4) + "L";
-            drawStringFixedCorner(amountStr, stackX + 17, stackY + 17, 16777215, true, 0.5f);
+
+            if (!pw.isStocking()) {
+                String amountStr = TextFormattingUtil.formatLongToCompactString(config.getStackSize(), 4) + "L";
+                drawStringFixedCorner(amountStr, stackX + 17, stackY + 17, 16777215, true, 0.5f);
+            }
         }
         if (stock != null) {
-            RenderUtil.drawFluidForGui(stock.getFluidStack(), stock.getFluidStack().amount, stackX, stackY + 18, 17,
-                    17);
+            RenderUtil.drawFluidForGui(stock.getFluidStack(), stock.getFluidStack().amount, stackX + DISPLAY_X_OFFSET,
+                    stackY, 17, 17);
             String amountStr = TextFormattingUtil.formatLongToCompactString(stock.getStackSize(), 4) + "L";
-            drawStringFixedCorner(amountStr, stackX + 17, stackY + 18 + 17, 16777215, true, 0.5f);
+            drawStringFixedCorner(amountStr, stackX + DISPLAY_X_OFFSET + 17, stackY + 17, 16777215, true, 0.5f);
         }
         if (mouseOverConfig(mouseX, mouseY)) {
             drawSelectionOverlay(stackX, stackY, 16, 16);
         } else if (mouseOverStock(mouseX, mouseY)) {
-            drawSelectionOverlay(stackX, stackY + 18, 16, 16);
+            drawSelectionOverlay(stackX + DISPLAY_X_OFFSET, stackY, 16, 16);
         }
     }
 
+    private void drawSlots(boolean autoPull, int x, int y) {
+        if (autoPull) {
+            GuiTextures.SLOT_DARK.draw(x, y, 18, 18);
+        } else {
+            GuiTextures.FLUID_SLOT.draw(x, y, 18, 18);
+        }
+        GuiTextures.SLOT_DARK.draw(x + DISPLAY_X_OFFSET, y, 18, 18);
+        GuiTextures.CONFIG_ARROW.draw(x, y, 18, 18);
+    }
+
     @Override
     public void drawInForeground(int mouseX, int mouseY) {
         super.drawInForeground(mouseX, mouseY);
@@ -107,13 +120,31 @@ public void drawInForeground(int mouseX, int mouseY) {
         }
     }
 
+    @Override
+    protected void addHoverText(List hoverText) {
+        if (getParentWidget().isAutoPull()) {
+            hoverText.add(I18n.format("gregtech.gui.config_slot"));
+            hoverText.add(I18n.format("gregtech.gui.config_slot.auto_pull_managed"));
+        } else {
+            super.addHoverText(hoverText);
+        }
+    }
+
     @Override
     public boolean mouseClicked(int mouseX, int mouseY, int button) {
+        AEFluidConfigWidget pw = getParentWidget();
+        if (pw.isAutoPull()) {
+            return false;
+        }
+
         if (mouseOverConfig(mouseX, mouseY)) {
             if (button == 1) {
                 // Right click to clear
-                this.parentWidget.disableAmount();
                 writeClientAction(REMOVE_ID, buf -> {});
+
+                if (!pw.isStocking()) {
+                    this.parentWidget.disableAmount();
+                }
             } else if (button == 0) {
                 // Left click to set/select
                 ItemStack hold = this.gui.entityPlayer.inventory.getItemStack();
@@ -125,8 +156,11 @@ public boolean mouseClicked(int mouseX, int mouseY, int button) {
                         buf.writeVarInt(fluid.amount);
                     });
                 }
-                this.parentWidget.enableAmount(this.index);
-                this.select = true;
+
+                if (!pw.isStocking()) {
+                    this.parentWidget.enableAmount(this.index);
+                    this.select = true;
+                }
             }
             return true;
         }
@@ -145,6 +179,7 @@ public void handleClientAction(int id, PacketBuffer buffer) {
         if (id == UPDATE_ID) {
             FluidStack fluid = FluidRegistry.getFluidStack(buffer.readString(Integer.MAX_VALUE / 16),
                     buffer.readVarInt());
+            if (!isFluidValidForSlot(fluid)) return;
             slot.setConfig(WrappedFluidStack.fromFluidStack(fluid));
             this.parentWidget.enableAmount(this.index);
             if (fluid != null) {
@@ -198,13 +233,20 @@ public void readUpdateInfo(int id, PacketBuffer buffer) {
         }
     }
 
+    private boolean isFluidValidForSlot(FluidStack stack) {
+        if (stack == null) return true;
+        AEFluidConfigWidget pw = getParentWidget();
+        if (!pw.isStocking()) return true;
+        return !pw.hasStackInConfig(stack);
+    }
+
     @Override
     public List> getPhantomTargets(Object ingredient) {
         if (getFluidFromContainer(ingredient) == null) {
             return Collections.emptyList();
         }
         Rectangle rectangle = toRectangleBox();
-        rectangle.height /= 2;
+        rectangle.width /= 6;
         return Lists.newArrayList(new IGhostIngredientHandler.Target<>() {
 
             @NotNull
@@ -227,9 +269,10 @@ public void accept(@NotNull Object ingredient) {
 
     @SideOnly(Side.CLIENT)
     public boolean mouseWheelMove(int mouseX, int mouseY, int wheelDelta) {
+        if (parentWidget.isStocking()) return false;
         IConfigurableSlot slot = this.parentWidget.getDisplay(this.index);
         Rectangle rectangle = toRectangleBox();
-        rectangle.height /= 2;
+        rectangle.width /= 6;
         if (slot.getConfig() == null || wheelDelta == 0 || !rectangle.contains(mouseX, mouseY)) {
             return false;
         }
diff --git a/src/main/java/gregtech/common/gui/widget/appeng/slot/AEItemConfigSlot.java b/src/main/java/gregtech/common/gui/widget/appeng/slot/AEItemConfigSlot.java
index 02cad40272b..3dff4140249 100644
--- a/src/main/java/gregtech/common/gui/widget/appeng/slot/AEItemConfigSlot.java
+++ b/src/main/java/gregtech/common/gui/widget/appeng/slot/AEItemConfigSlot.java
@@ -5,10 +5,11 @@
 import gregtech.api.util.Position;
 import gregtech.api.util.Size;
 import gregtech.api.util.TextFormattingUtil;
-import gregtech.common.gui.widget.appeng.AEConfigWidget;
-import gregtech.common.metatileentities.multi.multiblockpart.appeng.IConfigurableSlot;
+import gregtech.common.gui.widget.appeng.AEItemConfigWidget;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.IConfigurableSlot;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedItemStack;
 
+import net.minecraft.client.resources.I18n;
 import net.minecraft.item.ItemStack;
 import net.minecraft.network.PacketBuffer;
 import net.minecraftforge.fml.relauncher.Side;
@@ -24,27 +25,26 @@
 import java.util.Collections;
 import java.util.List;
 
-/**
- * @Author GlodBlock
- * @Description A configurable slot for {@link IAEItemStack}
- * @Date 2023/4/22-0:48
- */
 public class AEItemConfigSlot extends AEConfigSlot {
 
-    public AEItemConfigSlot(int x, int y, AEConfigWidget widget, int index) {
-        super(new Position(x, y), new Size(18, 18 * 2), widget, index);
+    public AEItemConfigSlot(int x, int y, AEItemConfigWidget widget, int index) {
+        super(new Position(x, y), new Size(18 * 6, 18), widget, index);
+    }
+
+    @Override
+    public AEItemConfigWidget getParentWidget() {
+        return (AEItemConfigWidget) super.getParentWidget();
     }
 
     @Override
     public void drawInBackground(int mouseX, int mouseY, float partialTicks, IRenderContext context) {
         super.drawInBackground(mouseX, mouseY, partialTicks, context);
+        AEItemConfigWidget pw = getParentWidget();
         Position position = getPosition();
-        IConfigurableSlot slot = this.parentWidget.getDisplay(this.index);
+        IConfigurableSlot slot = pw.getDisplay(this.index);
         IAEItemStack config = slot.getConfig();
         IAEItemStack stock = slot.getStock();
-        GuiTextures.SLOT.draw(position.x, position.y, 18, 18);
-        GuiTextures.SLOT.draw(position.x, position.y + 18, 18, 18);
-        GuiTextures.CONFIG_ARROW_DARK.draw(position.x, position.y, 18, 18);
+        drawSlots(pw.isAutoPull(), position.x, position.y);
         if (this.select) {
             GuiTextures.SELECT_BOX.draw(position.x, position.y, 18, 18);
         }
@@ -54,28 +54,43 @@ public void drawInBackground(int mouseX, int mouseY, float partialTicks, IRender
             ItemStack stack = config.createItemStack();
             stack.setCount(1);
             drawItemStack(stack, stackX, stackY, null);
-            String amountStr = TextFormattingUtil.formatLongToCompactString(config.getStackSize(), 4);
-            drawStringFixedCorner(amountStr, stackX + 17, stackY + 17, 16777215, true, 0.5f);
+
+            // Only draw the config amount if not stocking, as its meaningless when stocking
+            if (!pw.isStocking()) {
+                String amountStr = TextFormattingUtil.formatLongToCompactString(config.getStackSize(), 4);
+                drawStringFixedCorner(amountStr, stackX + 17, stackY + 17, 16777215, true, 0.5f);
+            }
         }
         if (stock != null) {
             ItemStack stack = stock.createItemStack();
             stack.setCount(1);
-            drawItemStack(stack, stackX, stackY + 18, null);
+            drawItemStack(stack, stackX + DISPLAY_X_OFFSET, stackY, null);
             String amountStr = TextFormattingUtil.formatLongToCompactString(stock.getStackSize(), 4);
-            drawStringFixedCorner(amountStr, stackX + 17, stackY + 18 + 17, 16777215, true, 0.5f);
+            drawStringFixedCorner(amountStr, stackX + DISPLAY_X_OFFSET + 17, stackY + 17, 16777215, true, 0.5f);
         }
         if (mouseOverConfig(mouseX, mouseY)) {
             drawSelectionOverlay(stackX, stackY, 16, 16);
         } else if (mouseOverStock(mouseX, mouseY)) {
-            drawSelectionOverlay(stackX, stackY + 18, 16, 16);
+            drawSelectionOverlay(stackX + DISPLAY_X_OFFSET, stackY, 16, 16);
         }
     }
 
+    private void drawSlots(boolean autoPull, int x, int y) {
+        if (autoPull) {
+            GuiTextures.SLOT_DARK.draw(x, y, 18, 18);
+            GuiTextures.CONFIG_ARROW.draw(x, y, 18, 18);
+        } else {
+            GuiTextures.SLOT.draw(x, y, 18, 18);
+            GuiTextures.CONFIG_ARROW_DARK.draw(x, y, 18, 18);
+        }
+        GuiTextures.SLOT_DARK.draw(x + DISPLAY_X_OFFSET, y, 18, 18);
+    }
+
     @Override
     public void drawInForeground(int mouseX, int mouseY) {
         super.drawInForeground(mouseX, mouseY);
         IAEItemStack item = null;
-        IConfigurableSlot slot = this.parentWidget.getDisplay(this.index);
+        IConfigurableSlot slot = this.getParentWidget().getDisplay(this.index);
         if (mouseOverConfig(mouseX, mouseY)) {
             item = slot.getConfig();
         } else if (mouseOverStock(mouseX, mouseY)) {
@@ -86,22 +101,45 @@ public void drawInForeground(int mouseX, int mouseY) {
         }
     }
 
+    @Override
+    protected void addHoverText(List hoverText) {
+        if (getParentWidget().isAutoPull()) {
+            hoverText.add(I18n.format("gregtech.gui.config_slot"));
+            hoverText.add(I18n.format("gregtech.gui.config_slot.auto_pull_managed"));
+        } else {
+            super.addHoverText(hoverText);
+        }
+    }
+
     @Override
     public boolean mouseClicked(int mouseX, int mouseY, int button) {
+        AEItemConfigWidget pw = getParentWidget();
+        // don't allow manual interaction with config slots when auto pull is enabled
+        if (pw.isAutoPull()) {
+            return false;
+        }
+
         if (mouseOverConfig(mouseX, mouseY)) {
             if (button == 1) {
                 // Right click to clear
-                this.parentWidget.disableAmount();
                 writeClientAction(REMOVE_ID, buf -> {});
+
+                if (!pw.isStocking()) {
+                    pw.disableAmount();
+                }
             } else if (button == 0) {
                 // Left click to set/select
                 ItemStack item = this.gui.entityPlayer.inventory.getItemStack();
 
                 if (!item.isEmpty()) {
                     writeClientAction(UPDATE_ID, buf -> buf.writeItemStack(item));
+                    return true;
+                }
+
+                if (!pw.isStocking()) {
+                    pw.enableAmount(this.index);
+                    this.select = true;
                 }
-                this.parentWidget.enableAmount(this.index);
-                this.select = true;
             }
             return true;
         }
@@ -120,6 +158,7 @@ public void handleClientAction(int id, PacketBuffer buffer) {
         if (id == UPDATE_ID) {
             try {
                 ItemStack item = buffer.readItemStack();
+                if (!isItemValidForSlot(item)) return;
                 slot.setConfig(WrappedItemStack.fromItemStack(item));
                 this.parentWidget.enableAmount(this.index);
                 if (!item.isEmpty()) {
@@ -157,13 +196,21 @@ public void readUpdateInfo(int id, PacketBuffer buffer) {
         }
     }
 
+    // Method for server-side validation of an attempted new configured item
+    private boolean isItemValidForSlot(ItemStack stack) {
+        if (stack == null || stack.isEmpty()) return true;
+        AEItemConfigWidget pw = getParentWidget();
+        if (!pw.isStocking()) return true;
+        return !pw.hasStackInConfig(stack);
+    }
+
     @Override
     public List> getPhantomTargets(Object ingredient) {
         if (!(ingredient instanceof ItemStack)) {
             return Collections.emptyList();
         }
         Rectangle rectangle = toRectangleBox();
-        rectangle.height /= 2;
+        rectangle.width /= 6;
         return Lists.newArrayList(new IGhostIngredientHandler.Target<>() {
 
             @NotNull
@@ -183,9 +230,11 @@ public void accept(@NotNull Object ingredient) {
 
     @SideOnly(Side.CLIENT)
     public boolean mouseWheelMove(int mouseX, int mouseY, int wheelDelta) {
+        // Only allow the amount scrolling if not stocking, as amount is useless for stocking
+        if (parentWidget.isStocking()) return false;
         IConfigurableSlot slot = this.parentWidget.getDisplay(this.index);
         Rectangle rectangle = toRectangleBox();
-        rectangle.height /= 2;
+        rectangle.width /= 6;
         if (slot.getConfig() == null || wheelDelta == 0 || !rectangle.contains(mouseX, mouseY)) {
             return false;
         }
diff --git a/src/main/java/gregtech/common/gui/widget/appeng/slot/AmountSetSlot.java b/src/main/java/gregtech/common/gui/widget/appeng/slot/AmountSetSlot.java
index ccec8521dd7..35063d5638b 100644
--- a/src/main/java/gregtech/common/gui/widget/appeng/slot/AmountSetSlot.java
+++ b/src/main/java/gregtech/common/gui/widget/appeng/slot/AmountSetSlot.java
@@ -6,7 +6,7 @@
 import gregtech.api.gui.widgets.TextFieldWidget2;
 import gregtech.api.util.Position;
 import gregtech.common.gui.widget.appeng.AEConfigWidget;
-import gregtech.common.metatileentities.multi.multiblockpart.appeng.IConfigurableSlot;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.IConfigurableSlot;
 
 import net.minecraft.network.PacketBuffer;
 
diff --git a/src/main/java/gregtech/common/gui/widget/orefilter/OreFilterTestSlot.java b/src/main/java/gregtech/common/gui/widget/orefilter/OreFilterTestSlot.java
index 6bde8f6cb5b..fd81e80bcd7 100644
--- a/src/main/java/gregtech/common/gui/widget/orefilter/OreFilterTestSlot.java
+++ b/src/main/java/gregtech/common/gui/widget/orefilter/OreFilterTestSlot.java
@@ -23,7 +23,6 @@
 
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -32,6 +31,9 @@
  */
 public abstract class OreFilterTestSlot extends WidgetGroup {
 
+    private final ImageWidget match;
+    private final ImageWidget noMatch;
+
     @Nullable
     private OreGlob glob;
     private boolean expectedResult = true;
@@ -48,8 +50,7 @@ public abstract class OreFilterTestSlot extends WidgetGroup {
 
     private boolean initialized = false;
 
-    private final ImageWidget match;
-    private final ImageWidget noMatch;
+    private boolean matchAll;
 
     public OreFilterTestSlot(int xPosition, int yPosition) {
         super(xPosition, yPosition, 18, 18);
@@ -86,30 +87,42 @@ public OreFilterTestSlot onMatchChange(@Nullable BooleanConsumer onMatchChange)
     }
 
     public void setGlob(@Nullable OreGlob glob) {
+        if (this.glob == glob) return;
         this.glob = glob;
         updatePreview();
     }
 
+    public void setMatchAll(boolean matchAll) {
+        if (this.matchAll == matchAll) return;
+        this.matchAll = matchAll;
+        updatePreview();
+    }
+
     protected void updatePreview() {
         if (!this.initialized) return;
         Set oreDicts = getTestCandidates();
         if (oreDicts != null) {
-            boolean success;
             OreGlob glob = this.glob;
             if (oreDicts.isEmpty()) {
                 // no oredict entries
-                this.testResult = Object2BooleanMaps.singleton("", success = glob != null && glob.matches(""));
+                this.testResult = Object2BooleanMaps.singleton("", glob != null && glob.matches(""));
                 this.matchType = MatchType.NO_ORE_DICT_MATCH;
             } else {
                 this.testResult = new Object2BooleanAVLTreeMap<>();
-                success = false;
                 for (String oreDict : oreDicts) {
                     boolean matches = glob != null && glob.matches(oreDict);
                     this.testResult.put(oreDict, matches);
-                    success |= matches;
                 }
                 this.matchType = MatchType.ORE_DICT_MATCH;
             }
+            boolean success = this.matchAll;
+            for (var e : testResult.object2BooleanEntrySet()) {
+                boolean result = e.getBooleanValue();
+                if (result == !this.matchAll) {
+                    success = !this.matchAll;
+                    break;
+                }
+            }
             updateAndNotifyMatchSuccess(this.expectedResult == success);
             this.match.setVisible(this.expectedResult == success);
             this.noMatch.setVisible(this.expectedResult != success);
@@ -131,9 +144,8 @@ private void updateAndNotifyMatchSuccess(boolean newValue) {
     }
 
     /**
-     * Get each test candidate for current state of test slot. An empty collection indicates that the
-     * match is for items without any ore dictionary entry. A {@code null} value indicates that the
-     * input state is invalid or empty.
+     * Get each test candidate for current state of test slot. An empty collection indicates that the match is for items
+     * without any ore dictionary entry. A {@code null} value indicates that the input state is invalid or empty.
      *
      * @return each test candidate for current state of test slot
      */
@@ -168,26 +180,17 @@ public void drawInBackground(int mouseX, int mouseY, float partialTicks, IRender
     @Override
     public void drawInForeground(int mouseX, int mouseY) {
         if (isActive() && isMouseOverElement(mouseX, mouseY)) {
-            List list;
-            switch (this.matchType) {
-                case NO_ORE_DICT_MATCH:
-                    list = Collections.singletonList(I18n.format(this.matchSuccess ?
-                            "cover.ore_dictionary_filter.test_slot.no_oredict.matches" :
-                            "cover.ore_dictionary_filter.test_slot.no_oredict.matches_not"));
-                    break;
-                case ORE_DICT_MATCH:
-                    list = this.testResult.object2BooleanEntrySet().stream().map(
-                            e -> I18n.format(e.getBooleanValue() ?
-                                    "cover.ore_dictionary_filter.test_slot.matches" :
-                                    "cover.ore_dictionary_filter.test_slot.matches_not", e.getKey()))
-                            .collect(Collectors.toList());
-                    break;
-                case INVALID:
-                default:
-                    list = Arrays.asList(LocalizationUtils.formatLines("cover.ore_dictionary_filter.test_slot.info"));
-                    break;
-            }
-            drawHoveringText(ItemStack.EMPTY, list, 300, mouseX, mouseY);
+            drawHoveringText(ItemStack.EMPTY, switch (this.matchType) {
+                case NO_ORE_DICT_MATCH -> Collections.singletonList(I18n.format(this.matchSuccess ?
+                        "cover.ore_dictionary_filter.test_slot.no_oredict.matches" :
+                        "cover.ore_dictionary_filter.test_slot.no_oredict.matches_not"));
+                case ORE_DICT_MATCH -> this.testResult.object2BooleanEntrySet().stream().map(
+                        e -> I18n.format(e.getBooleanValue() ?
+                                "cover.ore_dictionary_filter.test_slot.matches" :
+                                "cover.ore_dictionary_filter.test_slot.matches_not", e.getKey()))
+                        .collect(Collectors.toList());
+                default -> Arrays.asList(LocalizationUtils.formatLines("cover.ore_dictionary_filter.test_slot.info"));
+            }, 300, mouseX, mouseY);
         }
     }
 
diff --git a/src/main/java/gregtech/common/items/MetaItem1.java b/src/main/java/gregtech/common/items/MetaItem1.java
index 9620ccaa60b..d194fedd4f5 100644
--- a/src/main/java/gregtech/common/items/MetaItem1.java
+++ b/src/main/java/gregtech/common/items/MetaItem1.java
@@ -3,7 +3,11 @@
 import gregtech.api.GTValues;
 import gregtech.api.GregTechAPI;
 import gregtech.api.capability.impl.CommonFluidFilters;
-import gregtech.api.items.metaitem.*;
+import gregtech.api.items.metaitem.ElectricStats;
+import gregtech.api.items.metaitem.FilteredFluidStats;
+import gregtech.api.items.metaitem.FoodStats;
+import gregtech.api.items.metaitem.MusicDiscStats;
+import gregtech.api.items.metaitem.StandardMetaItem;
 import gregtech.api.items.metaitem.stats.IItemComponent;
 import gregtech.api.items.metaitem.stats.IItemContainerItemProvider;
 import gregtech.api.items.metaitem.stats.ItemFluidContainer;
@@ -22,8 +26,27 @@
 import gregtech.api.util.RandomPotionEffect;
 import gregtech.common.ConfigHolder;
 import gregtech.common.blocks.MetaBlocks;
+import gregtech.common.creativetab.GTCreativeTabs;
 import gregtech.common.entities.GTBoatEntity.GTBoatType;
-import gregtech.common.items.behaviors.*;
+import gregtech.common.items.behaviors.ClipboardBehavior;
+import gregtech.common.items.behaviors.ColorSprayBehaviour;
+import gregtech.common.items.behaviors.DataItemBehavior;
+import gregtech.common.items.behaviors.DoorBehavior;
+import gregtech.common.items.behaviors.DynamiteBehaviour;
+import gregtech.common.items.behaviors.FacadeItem;
+import gregtech.common.items.behaviors.FertilizerBehavior;
+import gregtech.common.items.behaviors.FoamSprayerBehavior;
+import gregtech.common.items.behaviors.GTBoatBehavior;
+import gregtech.common.items.behaviors.IntCircuitBehaviour;
+import gregtech.common.items.behaviors.ItemMagnetBehavior;
+import gregtech.common.items.behaviors.LighterBehaviour;
+import gregtech.common.items.behaviors.MultiblockBuilderBehavior;
+import gregtech.common.items.behaviors.NanoSaberBehavior;
+import gregtech.common.items.behaviors.ProspectorScannerBehavior;
+import gregtech.common.items.behaviors.TerminalBehaviour;
+import gregtech.common.items.behaviors.TooltipBehavior;
+import gregtech.common.items.behaviors.TricorderBehavior;
+import gregtech.common.items.behaviors.TurbineRotorBehavior;
 import gregtech.common.items.behaviors.monitorplugin.AdvancedMonitorPluginBehavior;
 import gregtech.common.items.behaviors.monitorplugin.FakeGuiPluginBehavior;
 import gregtech.common.items.behaviors.monitorplugin.OnlinePicPluginBehavior;
@@ -149,53 +172,53 @@ public void registerSubItems() {
         // out of registry order so it can reference the Empty Spray Can
         SPRAY_SOLVENT = addItem(60, "spray.solvent").setMaxStackSize(1)
                 .addComponents(new ColorSprayBehaviour(SPRAY_EMPTY.getStackForm(), 1024, -1))
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         for (int i = 0; i < EnumDyeColor.values().length; i++) {
             SPRAY_CAN_DYES[i] = addItem(62 + i, "spray.can.dyes." + EnumDyeColor.values()[i].getName())
                     .setMaxStackSize(1)
                     .addComponents(new ColorSprayBehaviour(SPRAY_EMPTY.getStackForm(), 512, i))
-                    .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                    .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         }
 
         // Fluid Cells: ID 78-88
         FLUID_CELL = addItem(78, "fluid_cell")
                 .addComponents(new FilteredFluidStats(1000, 1800, true, false, false, false, false),
                         new ItemFluidContainer())
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         FLUID_CELL_UNIVERSAL = addItem(79, "fluid_cell.universal")
                 .addComponents(new FilteredFluidStats(1000, 1800, true, false, false, false, true),
                         new ItemFluidContainer())
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         FLUID_CELL_LARGE_STEEL = addItem(80, "large_fluid_cell.steel")
                 .addComponents(new FilteredFluidStats(8000,
                         Materials.Steel.getProperty(PropertyKey.FLUID_PIPE).getMaxFluidTemperature(), true, false,
                         false, false, true), new ItemFluidContainer())
                 .setMaterialInfo(new ItemMaterialInfo(new MaterialStack(Materials.Steel, M * 4))) // ingot * 4
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         FLUID_CELL_LARGE_ALUMINIUM = addItem(81, "large_fluid_cell.aluminium")
                 .addComponents(new FilteredFluidStats(32000,
                         Materials.Aluminium.getProperty(PropertyKey.FLUID_PIPE).getMaxFluidTemperature(), true, false,
                         false, false, true), new ItemFluidContainer())
                 .setMaterialInfo(new ItemMaterialInfo(new MaterialStack(Materials.Aluminium, M * 4))) // ingot * 4
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         FLUID_CELL_LARGE_STAINLESS_STEEL = addItem(82, "large_fluid_cell.stainless_steel")
                 .addComponents(new FilteredFluidStats(64000,
                         Materials.StainlessSteel.getProperty(PropertyKey.FLUID_PIPE).getMaxFluidTemperature(), true,
                         true, true, false, true), new ItemFluidContainer())
                 .setMaterialInfo(new ItemMaterialInfo(new MaterialStack(Materials.StainlessSteel, M * 6))) // ingot * 6
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         FLUID_CELL_LARGE_TITANIUM = addItem(83, "large_fluid_cell.titanium")
                 .addComponents(new FilteredFluidStats(128000,
                         Materials.Titanium.getProperty(PropertyKey.FLUID_PIPE).getMaxFluidTemperature(), true, true,
                         false, false, true), new ItemFluidContainer())
                 .setMaterialInfo(new ItemMaterialInfo(new MaterialStack(Materials.Titanium, M * 6))) // ingot * 6
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         FLUID_CELL_LARGE_TUNGSTEN_STEEL = addItem(84, "large_fluid_cell.tungstensteel")
                 .addComponents(new FilteredFluidStats(512000,
@@ -203,36 +226,36 @@ public void registerSubItems() {
                         true, false, false, true), new ItemFluidContainer())
                 .setMaxStackSize(32)
                 .setMaterialInfo(new ItemMaterialInfo(new MaterialStack(Materials.TungstenSteel, M * 8))) // ingot * 8
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         FLUID_CELL_GLASS_VIAL = addItem(85, "fluid_cell.glass_vial")
                 .addComponents(new FilteredFluidStats(1000, 1200, false, true, false, false, true),
                         new ItemFluidContainer())
                 .setMaterialInfo(new ItemMaterialInfo(new MaterialStack(Materials.Glass, M / 4))) // small dust
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         // Limited-Use Items: ID 89-95
 
         TOOL_MATCHES = addItem(89, "tool.matches")
                 .addComponents(new LighterBehaviour(false, false, false))
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         TOOL_MATCHBOX = addItem(90, "tool.matchbox")
                 .addComponents(new LighterBehaviour(false, true, false, Items.PAPER, 16))
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         TOOL_LIGHTER_INVAR = addItem(91, "tool.lighter.invar")
                 .setMaterialInfo(new ItemMaterialInfo(new MaterialStack(Materials.Invar, M * 2)))
                 .addComponents(new LighterBehaviour(GTUtility.gregtechId("lighter_open"), true, true, true))
                 .addComponents(new FilteredFluidStats(100, true, CommonFluidFilters.LIGHTER_FUEL))
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         TOOL_LIGHTER_PLATINUM = addItem(92, "tool.lighter.platinum")
                 .setMaterialInfo(new ItemMaterialInfo(new MaterialStack(Materials.Platinum, M * 2)))
                 .addComponents(new LighterBehaviour(GTUtility.gregtechId("lighter_open"), true, true, true))
                 .addComponents(new FilteredFluidStats(1000, true, CommonFluidFilters.LIGHTER_FUEL))
                 .setMaxStackSize(1)
                 .setRarity(EnumRarity.UNCOMMON)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         BOTTLE_PURPLE_DRINK = addItem(93, "bottle.purple.drink").addComponents(new FoodStats(8, 0.2F, true, true,
                 new ItemStack(Items.GLASS_BOTTLE), new RandomPotionEffect(MobEffects.HASTE, 800, 1, 90)));
@@ -737,69 +760,69 @@ public void registerSubItems() {
         // Usable Items: ID 460-490
         DYNAMITE = addItem(460, "dynamite")
                 .addComponents(new DynamiteBehaviour())
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         INTEGRATED_CIRCUIT = addItem(461, "circuit.integrated").addComponents(new IntCircuitBehaviour())
                 .setModelAmount(33);
         FOAM_SPRAYER = addItem(462, "foam_sprayer").addComponents(new FoamSprayerBehavior())
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         NANO_SABER = addItem(463, "nano_saber").addComponents(ElectricStats.createElectricItem(4_000_000L, GTValues.HV))
                 .addComponents(new NanoSaberBehavior())
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         NANO_SABER.getMetaItem().addPropertyOverride(NanoSaberBehavior.OVERRIDE_KEY_LOCATION,
                 (stack, worldIn, entityIn) -> NanoSaberBehavior.isItemActive(stack) ? 1.0f : 0.0f);
 
         CLIPBOARD = addItem(464, "clipboard")
                 .addComponents(new ClipboardBehavior())
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         TERMINAL = addItem(465, "terminal")
                 .addComponents(new HardwareProvider(), new TerminalBehaviour())
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         PROSPECTOR_LV = addItem(466, "prospector.lv")
                 .addComponents(ElectricStats.createElectricItem(100_000L, GTValues.LV),
                         new ProspectorScannerBehavior(2, GTValues.LV))
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         PROSPECTOR_HV = addItem(467, "prospector.hv")
                 .addComponents(ElectricStats.createElectricItem(1_600_000L, GTValues.HV),
                         new ProspectorScannerBehavior(3, GTValues.HV))
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         PROSPECTOR_LUV = addItem(468, "prospector.luv")
                 .addComponents(ElectricStats.createElectricItem(1_000_000_000L, GTValues.LuV),
                         new ProspectorScannerBehavior(5, GTValues.LuV))
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         TRICORDER_SCANNER = addItem(469, "tricorder_scanner")
                 .addComponents(ElectricStats.createElectricItem(100_000L, GTValues.MV), new TricorderBehavior(2))
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         DEBUG_SCANNER = addItem(470, "debug_scanner")
                 .addComponents(new TricorderBehavior(3))
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         ITEM_MAGNET_LV = addItem(471, "item_magnet.lv")
                 .addComponents(ElectricStats.createElectricItem(100_000L, GTValues.LV), new ItemMagnetBehavior(8))
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         ITEM_MAGNET_HV = addItem(472, "item_magnet.hv")
                 .addComponents(ElectricStats.createElectricItem(1_600_000L, GTValues.HV), new ItemMagnetBehavior(32))
                 .setMaxStackSize(1)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         RUBBER_WOOD_BOAT = addItem(473, "rubber_wood_boat")
                 .addComponents(new GTBoatBehavior(GTBoatType.RUBBER_WOOD_BOAT)).setMaxStackSize(1).setBurnValue(400);
         TREATED_WOOD_BOAT = addItem(474, "treated_wood_boat")
                 .addComponents(new GTBoatBehavior(GTBoatType.TREATED_WOOD_BOAT)).setMaxStackSize(1).setBurnValue(400);
         RUBBER_WOOD_DOOR = addItem(475, "rubber_wood_door").addComponents(new DoorBehavior(MetaBlocks.RUBBER_WOOD_DOOR))
-                .setBurnValue(200).setCreativeTabs(GregTechAPI.TAB_GREGTECH_DECORATIONS);
+                .setBurnValue(200).setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_DECORATIONS);
         TREATED_WOOD_DOOR = addItem(476, "treated_wood_door")
                 .addComponents(new DoorBehavior(MetaBlocks.TREATED_WOOD_DOOR)).setBurnValue(200)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_DECORATIONS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_DECORATIONS);
 
         // Misc Crafting Items: ID 491-515
         ENERGIUM_DUST = addItem(491, "energium_dust");
@@ -976,105 +999,105 @@ public void registerSubItems() {
         // Batteries: 731-775
         BATTERY_ULV_TANTALUM = addItem(731, "battery.re.ulv.tantalum")
                 .addComponents(ElectricStats.createRechargeableBattery(1000, GTValues.ULV))
-                .setUnificationData(OrePrefix.battery, Tier.ULV).setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setUnificationData(OrePrefix.battery, Tier.ULV).setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         BATTERY_LV_SODIUM = addItem(732, "battery.re.lv.sodium")
                 .addComponents(ElectricStats.createRechargeableBattery(80000, GTValues.LV))
                 .setUnificationData(OrePrefix.battery, Tier.LV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         BATTERY_MV_SODIUM = addItem(733, "battery.re.mv.sodium")
                 .addComponents(ElectricStats.createRechargeableBattery(360000, GTValues.MV))
                 .setUnificationData(OrePrefix.battery, Tier.MV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         BATTERY_HV_SODIUM = addItem(734, "battery.re.hv.sodium")
                 .addComponents(ElectricStats.createRechargeableBattery(1200000, GTValues.HV))
                 .setUnificationData(OrePrefix.battery, Tier.HV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         BATTERY_LV_LITHIUM = addItem(735, "battery.re.lv.lithium")
                 .addComponents(ElectricStats.createRechargeableBattery(120000, GTValues.LV))
                 .setUnificationData(OrePrefix.battery, Tier.LV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         BATTERY_MV_LITHIUM = addItem(736, "battery.re.mv.lithium")
                 .addComponents(ElectricStats.createRechargeableBattery(420000, GTValues.MV))
                 .setUnificationData(OrePrefix.battery, Tier.MV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         BATTERY_HV_LITHIUM = addItem(737, "battery.re.hv.lithium")
                 .addComponents(ElectricStats.createRechargeableBattery(1800000, GTValues.HV))
                 .setUnificationData(OrePrefix.battery, Tier.HV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         BATTERY_LV_CADMIUM = addItem(738, "battery.re.lv.cadmium")
                 .addComponents(ElectricStats.createRechargeableBattery(100000, GTValues.LV))
                 .setUnificationData(OrePrefix.battery, Tier.LV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         BATTERY_MV_CADMIUM = addItem(739, "battery.re.mv.cadmium")
                 .addComponents(ElectricStats.createRechargeableBattery(400000, GTValues.MV))
                 .setUnificationData(OrePrefix.battery, Tier.MV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         BATTERY_HV_CADMIUM = addItem(740, "battery.re.hv.cadmium")
                 .addComponents(ElectricStats.createRechargeableBattery(1600000, GTValues.HV))
                 .setUnificationData(OrePrefix.battery, Tier.HV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         ENERGIUM_CRYSTAL = addItem(741, "energy_crystal")
                 .addComponents(ElectricStats.createRechargeableBattery(6_400_000L, GTValues.HV))
                 .setUnificationData(OrePrefix.battery, Tier.HV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         LAPOTRON_CRYSTAL = addItem(742, "lapotron_crystal")
                 .addComponents(ElectricStats.createRechargeableBattery(25_000_000L, GTValues.EV))
                 .setUnificationData(OrePrefix.battery, Tier.EV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         BATTERY_EV_VANADIUM = addItem(743, "battery.ev.vanadium")
                 .addComponents(ElectricStats.createRechargeableBattery(10_240_000L, GTValues.EV))
                 .setUnificationData(OrePrefix.battery, Tier.EV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         BATTERY_IV_VANADIUM = addItem(744, "battery.iv.vanadium")
                 .addComponents(ElectricStats.createRechargeableBattery(40_960_000L, GTValues.IV))
                 .setUnificationData(OrePrefix.battery, Tier.IV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         BATTERY_LUV_VANADIUM = addItem(745, "battery.luv.vanadium")
                 .addComponents(ElectricStats.createRechargeableBattery(163_840_000L, GTValues.LuV))
                 .setUnificationData(OrePrefix.battery, Tier.LuV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         BATTERY_ZPM_NAQUADRIA = addItem(746, "battery.zpm.naquadria")
                 .addComponents(ElectricStats.createRechargeableBattery(655_360_000L, GTValues.ZPM))
                 .setUnificationData(OrePrefix.battery, Tier.ZPM).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         BATTERY_UV_NAQUADRIA = addItem(747, "battery.uv.naquadria")
                 .addComponents(ElectricStats.createRechargeableBattery(2_621_440_000L, GTValues.UV))
                 .setUnificationData(OrePrefix.battery, Tier.UV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         ENERGY_LAPOTRONIC_ORB = addItem(748, "energy.lapotronic_orb")
                 .addComponents(ElectricStats.createRechargeableBattery(250_000_000L, GTValues.IV))
                 .setUnificationData(OrePrefix.battery, Tier.IV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         ENERGY_LAPOTRONIC_ORB_CLUSTER = addItem(749, "energy.lapotronic_orb_cluster")
                 .addComponents(ElectricStats.createRechargeableBattery(1_000_000_000L, GTValues.LuV))
                 .setUnificationData(OrePrefix.battery, Tier.LuV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         ENERGY_MODULE = addItem(750, "energy.module")
                 .addComponents(
                         new IItemComponent[] { ElectricStats.createRechargeableBattery(4_000_000_000L, GTValues.ZPM) })
                 .setUnificationData(OrePrefix.battery, Tier.ZPM).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         ENERGY_CLUSTER = addItem(751, "energy.cluster")
                 .addComponents(
                         new IItemComponent[] { ElectricStats.createRechargeableBattery(20_000_000_000L, GTValues.UV) })
                 .setUnificationData(OrePrefix.battery, Tier.UV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         ZERO_POINT_MODULE = addItem(752, "zpm")
                 .addComponents(ElectricStats.createBattery(2000000000000L, GTValues.ZPM, true)).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
         ULTIMATE_BATTERY = addItem(753, "max.battery")
                 .addComponents(ElectricStats.createRechargeableBattery(Long.MAX_VALUE, GTValues.UHV))
                 .setUnificationData(OrePrefix.battery, Tier.UHV).setModelAmount(8)
-                .setCreativeTabs(GregTechAPI.TAB_GREGTECH_TOOLS);
+                .setCreativeTabs(GTCreativeTabs.TAB_GREGTECH_TOOLS);
 
         POWER_THRUSTER = addItem(776, "power_thruster").setRarity(EnumRarity.UNCOMMON);
         POWER_THRUSTER_ADVANCED = addItem(777, "power_thruster_advanced").setRarity(EnumRarity.RARE);
diff --git a/src/main/java/gregtech/common/items/ToolItems.java b/src/main/java/gregtech/common/items/ToolItems.java
index 05e2453846b..5545387ef7c 100644
--- a/src/main/java/gregtech/common/items/ToolItems.java
+++ b/src/main/java/gregtech/common/items/ToolItems.java
@@ -55,6 +55,9 @@ public final class ToolItems {
     public static IGTTool BUZZSAW;
     public static IGTTool SCREWDRIVER_LV;
     public static IGTTool PLUNGER;
+    public static IGTTool WIRECUTTER_LV;
+    public static IGTTool WIRECUTTER_HV;
+    public static IGTTool WIRECUTTER_IV;
 
     private ToolItems() {/**/}
 
@@ -319,6 +322,36 @@ public static void init() {
                 .oreDict(ToolOreDict.toolPlunger)
                 .toolClasses(ToolClasses.PLUNGER)
                 .markerItem(() -> ToolHelper.getAndSetToolData(PLUNGER, Materials.Rubber, 255, 1, 4F, 0F)));
+        WIRECUTTER_LV = register(ItemGTTool.Builder.of(GTValues.MODID, "wire_cutter_lv")
+                .toolStats(b -> b.blockBreaking().crafting().damagePerCraftingAction(4)
+                        .efficiencyMultiplier(2.0F)
+                        .attackDamage(-1.0F).attackSpeed(-2.4F)
+                        .brokenStack(ToolHelper.SUPPLY_POWER_UNIT_LV))
+                .sound(GTSoundEvents.WIRECUTTER_TOOL, true)
+                .oreDict(ToolOreDict.toolWireCutter)
+                .secondaryOreDicts("craftingToolWireCutter")
+                .toolClasses(ToolClasses.WIRE_CUTTER)
+                .electric(GTValues.LV));
+        WIRECUTTER_HV = register(ItemGTTool.Builder.of(GTValues.MODID, "wire_cutter_hv")
+                .toolStats(b -> b.blockBreaking().crafting().damagePerCraftingAction(4)
+                        .efficiencyMultiplier(3.0F)
+                        .attackDamage(-1.0F).attackSpeed(-2.4F)
+                        .brokenStack(ToolHelper.SUPPLY_POWER_UNIT_LV))
+                .sound(GTSoundEvents.WIRECUTTER_TOOL, true)
+                .oreDict(ToolOreDict.toolWireCutter)
+                .secondaryOreDicts("craftingToolWireCutter")
+                .toolClasses(ToolClasses.WIRE_CUTTER)
+                .electric(GTValues.HV));
+        WIRECUTTER_IV = register(ItemGTTool.Builder.of(GTValues.MODID, "wire_cutter_iv")
+                .toolStats(b -> b.blockBreaking().crafting().damagePerCraftingAction(4)
+                        .efficiencyMultiplier(4.0F)
+                        .attackDamage(-1.0F).attackSpeed(-2.4F)
+                        .brokenStack(ToolHelper.SUPPLY_POWER_UNIT_LV))
+                .sound(GTSoundEvents.WIRECUTTER_TOOL, true)
+                .oreDict(ToolOreDict.toolWireCutter)
+                .secondaryOreDicts("craftingToolWireCutter")
+                .toolClasses(ToolClasses.WIRE_CUTTER)
+                .electric(GTValues.IV));
     }
 
     public static IGTTool register(@NotNull ToolBuilder builder) {
diff --git a/src/main/java/gregtech/common/items/behaviors/ColorSprayBehaviour.java b/src/main/java/gregtech/common/items/behaviors/ColorSprayBehaviour.java
index 6cda26f8ce5..69bf9e9640c 100644
--- a/src/main/java/gregtech/common/items/behaviors/ColorSprayBehaviour.java
+++ b/src/main/java/gregtech/common/items/behaviors/ColorSprayBehaviour.java
@@ -1,11 +1,11 @@
 package gregtech.common.items.behaviors;
 
-import gregtech.api.GTValues;
 import gregtech.api.items.metaitem.stats.IItemDurabilityManager;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.util.GradientUtil;
+import gregtech.api.util.Mods;
 import gregtech.core.sound.GTSoundEvents;
 
 import net.minecraft.block.Block;
@@ -23,7 +23,6 @@
 import net.minecraft.util.*;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
-import net.minecraftforge.fml.common.Loader;
 
 import appeng.api.util.AEColor;
 import appeng.tile.networking.TileCableBus;
@@ -93,7 +92,7 @@ private boolean tryPaintSpecialBlock(EntityPlayer player, World world, BlockPos
             world.setBlockState(pos, newBlockState);
             return true;
         }
-        if (Loader.isModLoaded(GTValues.MODID_APPENG)) {
+        if (Mods.AppliedEnergistics2.isModLoaded()) {
             TileEntity te = world.getTileEntity(pos);
             if (te instanceof TileCableBus) {
                 TileCableBus cable = (TileCableBus) te;
@@ -146,7 +145,7 @@ private static boolean tryStripBlockColor(EntityPlayer player, World world, Bloc
         }
 
         // AE2 cable special case
-        if (Loader.isModLoaded(GTValues.MODID_APPENG)) {
+        if (Mods.AppliedEnergistics2.isModLoaded()) {
             if (te instanceof TileCableBus) {
                 TileCableBus cable = (TileCableBus) te;
                 // do not try to strip color if it is already colorless
diff --git a/src/main/java/gregtech/common/items/behaviors/IntCircuitBehaviour.java b/src/main/java/gregtech/common/items/behaviors/IntCircuitBehaviour.java
index 82a492eaa7b..2be3dcbc766 100644
--- a/src/main/java/gregtech/common/items/behaviors/IntCircuitBehaviour.java
+++ b/src/main/java/gregtech/common/items/behaviors/IntCircuitBehaviour.java
@@ -7,6 +7,7 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.mui.GTGuiTextures;
 import gregtech.api.mui.GTGuis;
+import gregtech.api.mui.factory.MetaItemGuiFactory;
 import gregtech.api.recipes.ingredients.IntCircuitIngredient;
 import gregtech.api.util.GTUtility;
 
@@ -21,7 +22,7 @@
 import com.cleanroommc.modularui.api.drawable.IKey;
 import com.cleanroommc.modularui.api.widget.IWidget;
 import com.cleanroommc.modularui.drawable.ItemDrawable;
-import com.cleanroommc.modularui.manager.GuiCreationContext;
+import com.cleanroommc.modularui.factory.HandGuiData;
 import com.cleanroommc.modularui.screen.ModularPanel;
 import com.cleanroommc.modularui.value.sync.GuiSyncManager;
 import com.cleanroommc.modularui.value.sync.InteractionSyncHandler;
@@ -57,23 +58,22 @@ public ActionResult onItemUse(EntityPlayer player, World world, Block
     public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
         ItemStack heldItem = player.getHeldItem(hand);
         if (!world.isRemote) {
-            GTGuis.getMetaItemUiInfo(hand).open(player);
+            MetaItemGuiFactory.open(player, hand);
         }
         return ActionResult.newResult(EnumActionResult.SUCCESS, heldItem);
     }
 
     @Override
-    public ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManager guiSyncManager,
-                                boolean isClient) {
-        ItemDrawable circuitPreview = new ItemDrawable(guiCreationContext.getUsedItemStack());
+    public ModularPanel buildUI(HandGuiData guiData, GuiSyncManager guiSyncManager) {
+        ItemDrawable circuitPreview = new ItemDrawable(guiData.getUsedItemStack());
         for (int i = 0; i <= 32; i++) {
             int finalI = i;
             guiSyncManager.syncValue("config", i, new InteractionSyncHandler()
                     .setOnMousePressed(b -> {
                         ItemStack item = IntCircuitIngredient.getIntegratedCircuit(finalI);
-                        item.setCount(guiCreationContext.getUsedItemStack().getCount());
+                        item.setCount(guiData.getUsedItemStack().getCount());
                         circuitPreview.setItem(item);
-                        guiCreationContext.getPlayer().setHeldItem(guiCreationContext.getUsedHand(), item);
+                        guiData.getPlayer().setHeldItem(guiData.getHand(), item);
                     }));
         }
 
@@ -87,10 +87,11 @@ public ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManage
                         .size(18)
                         .background(GTGuiTextures.SLOT,
                                 new ItemDrawable(IntCircuitIngredient.getIntegratedCircuit(index)).asIcon().size(16))
+                        .disableHoverBackground()
                         .syncHandler("config", index));
             }
         }
-        return GTGuis.createPanel(guiCreationContext.getUsedItemStack(), 176, 120)
+        return GTGuis.createPanel(guiData.getUsedItemStack(), 176, 120)
                 .child(IKey.lang("metaitem.circuit.integrated.gui").asWidget().pos(5, 5))
                 .child(circuitPreview.asIcon().size(16).asWidget()
                         .size(18)
diff --git a/src/main/java/gregtech/common/items/behaviors/ItemMagnetBehavior.java b/src/main/java/gregtech/common/items/behaviors/ItemMagnetBehavior.java
index 15a3ebc2599..f5ce22ba925 100644
--- a/src/main/java/gregtech/common/items/behaviors/ItemMagnetBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/ItemMagnetBehavior.java
@@ -5,6 +5,7 @@
 import gregtech.api.capability.IElectricItem;
 import gregtech.api.items.metaitem.MetaItem;
 import gregtech.api.items.metaitem.stats.IItemBehaviour;
+import gregtech.api.util.Mods;
 import gregtech.integration.baubles.BaublesModule;
 
 import net.minecraft.client.resources.I18n;
@@ -26,7 +27,6 @@
 import net.minecraftforge.common.MinecraftForge;
 import net.minecraftforge.event.entity.item.ItemTossEvent;
 import net.minecraftforge.event.entity.player.PlayerPickupXpEvent;
-import net.minecraftforge.fml.common.Loader;
 import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
 
 import org.jetbrains.annotations.NotNull;
@@ -162,7 +162,7 @@ public void onItemToss(@NotNull ItemTossEvent event) {
         if (event.getPlayer() == null) return;
 
         IInventory inventory = event.getPlayer().inventory;
-        if (Loader.isModLoaded(GTValues.MODID_BAUBLES)) {
+        if (Mods.Baubles.isModLoaded()) {
             inventory = BaublesModule.getBaublesWrappedInventory(event.getPlayer());
         }
 
diff --git a/src/main/java/gregtech/common/items/behaviors/LighterBehaviour.java b/src/main/java/gregtech/common/items/behaviors/LighterBehaviour.java
index 4e1f14cfa61..bdff9524d0d 100644
--- a/src/main/java/gregtech/common/items/behaviors/LighterBehaviour.java
+++ b/src/main/java/gregtech/common/items/behaviors/LighterBehaviour.java
@@ -7,6 +7,7 @@
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.GTUtility;
 import gregtech.api.util.GradientUtil;
+import gregtech.common.blocks.explosive.BlockGTExplosive;
 
 import net.minecraft.advancements.CriteriaTriggers;
 import net.minecraft.block.Block;
@@ -24,7 +25,13 @@
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
-import net.minecraft.util.*;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.EnumActionResult;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.NonNullList;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.SoundCategory;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 import net.minecraftforge.fluids.FluidStack;
@@ -89,26 +96,38 @@ public boolean onLeftClickEntity(ItemStack stack, EntityPlayer player, Entity en
     }
 
     @Override
-    public EnumActionResult onItemUseFirst(@NotNull EntityPlayer player, @NotNull World world, BlockPos pos,
-                                           EnumFacing side, float hitX, float hitY, float hitZ, EnumHand hand) {
+    public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
         ItemStack stack = player.getHeldItem(hand);
         NBTTagCompound compound = GTUtility.getOrCreateNbtCompound(stack);
 
         if (canOpen && player.isSneaking()) {
             compound.setBoolean(LIGHTER_OPEN, !compound.getBoolean(LIGHTER_OPEN));
             stack.setTagCompound(compound);
-            return EnumActionResult.PASS;
         }
+        return ActionResult.newResult(EnumActionResult.PASS, stack);
+    }
+
+    @Override
+    public EnumActionResult onItemUseFirst(@NotNull EntityPlayer player, @NotNull World world, BlockPos pos,
+                                           EnumFacing side, float hitX, float hitY, float hitZ, EnumHand hand) {
+        ItemStack stack = player.getHeldItem(hand);
+        NBTTagCompound compound = GTUtility.getOrCreateNbtCompound(stack);
 
         if (!player.canPlayerEdit(pos, side, player.getHeldItem(hand))) return EnumActionResult.FAIL;
         // If this item does not have opening mechanics, or if it does and is currently open
-        if ((!canOpen || compound.getBoolean(LIGHTER_OPEN)) && consumeFuel(player, stack)) {
+        // If the item has opening mechanics, and the player is sneaking, close the item instead
+        if ((!canOpen || (compound.getBoolean(LIGHTER_OPEN)) && !player.isSneaking()) && consumeFuel(player, stack)) {
             player.getEntityWorld().playSound(null, player.getPosition(), SoundEvents.ITEM_FLINTANDSTEEL_USE,
                     SoundCategory.PLAYERS, 1.0F, GTValues.RNG.nextFloat() * 0.4F + 0.8F);
             IBlockState blockState = world.getBlockState(pos);
             Block block = blockState.getBlock();
-            if (block instanceof BlockTNT) {
-                ((BlockTNT) block).explode(world, pos, blockState.withProperty(BlockTNT.EXPLODE, true), player);
+            if (block instanceof BlockTNT tnt) {
+                tnt.explode(world, pos, blockState.withProperty(BlockTNT.EXPLODE, true), player);
+                world.setBlockState(pos, Blocks.AIR.getDefaultState(), 11);
+                return EnumActionResult.SUCCESS;
+            }
+            if (block instanceof BlockGTExplosive powderbarrel) {
+                powderbarrel.explode(world, pos, player);
                 world.setBlockState(pos, Blocks.AIR.getDefaultState(), 11);
                 return EnumActionResult.SUCCESS;
             }
diff --git a/src/main/java/gregtech/common/items/behaviors/TricorderBehavior.java b/src/main/java/gregtech/common/items/behaviors/TricorderBehavior.java
index 344b97231d3..82a2f46c854 100644
--- a/src/main/java/gregtech/common/items/behaviors/TricorderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/TricorderBehavior.java
@@ -63,7 +63,9 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
 
             List info = getScannerInfo(player, world, pos);
             if (player.isCreative() || drainEnergy(player.getHeldItem(hand), energyCost, true)) {
-                drainEnergy(player.getHeldItem(hand), energyCost, false);
+                if (!player.isCreative()) {
+                    drainEnergy(player.getHeldItem(hand), energyCost, false);
+                }
                 for (ITextComponent line : info) {
                     player.sendMessage(line);
                 }
diff --git a/src/main/java/gregtech/common/metatileentities/MetaTileEntities.java b/src/main/java/gregtech/common/metatileentities/MetaTileEntities.java
index d35d77f79d8..9f01ef182e5 100644
--- a/src/main/java/gregtech/common/metatileentities/MetaTileEntities.java
+++ b/src/main/java/gregtech/common/metatileentities/MetaTileEntities.java
@@ -13,6 +13,7 @@
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.GTLog;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.Mods;
 import gregtech.client.particle.VanillaParticleEffects;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
@@ -94,6 +95,8 @@
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEInputHatch;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEOutputBus;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEOutputHatch;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEStockingBus;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.MetaTileEntityMEStockingHatch;
 import gregtech.common.metatileentities.multi.multiblockpart.hpca.MetaTileEntityHPCABridge;
 import gregtech.common.metatileentities.multi.multiblockpart.hpca.MetaTileEntityHPCAComputation;
 import gregtech.common.metatileentities.multi.multiblockpart.hpca.MetaTileEntityHPCACooler;
@@ -128,7 +131,6 @@
 import gregtech.integration.jei.multiblock.MultiblockInfoCategory;
 
 import net.minecraft.util.ResourceLocation;
-import net.minecraftforge.fml.common.Loader;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -139,91 +141,65 @@
 
 public class MetaTileEntities {
 
+    // spotless:off
+
     // HULLS
     public static final MetaTileEntityHull[] HULL = new MetaTileEntityHull[GTValues.V.length];
-    public static final MetaTileEntityTransformer[] TRANSFORMER = new MetaTileEntityTransformer[GTValues.V.length - 1]; // no
-                                                                                                                        // MAX
-    public static final MetaTileEntityTransformer[] HI_AMP_TRANSFORMER = new MetaTileEntityTransformer[GTValues.V.length -
-            1]; /// no MAX
-    public static final MetaTileEntityTransformer[] POWER_TRANSFORMER = new MetaTileEntityTransformer[GTValues.V.length -
-            1]; // no MAX
+    public static final MetaTileEntityTransformer[] TRANSFORMER = new MetaTileEntityTransformer[GTValues.V.length - 1]; // no MAX
+    public static final MetaTileEntityTransformer[] HI_AMP_TRANSFORMER = new MetaTileEntityTransformer[GTValues.V.length - 1]; /// no MAX
+    public static final MetaTileEntityTransformer[] POWER_TRANSFORMER = new MetaTileEntityTransformer[GTValues.V.length - 1]; // no MAX
     public static final MetaTileEntityDiode[] DIODES = new MetaTileEntityDiode[GTValues.V.length];
     public static final MetaTileEntityBatteryBuffer[][] BATTERY_BUFFER = new MetaTileEntityBatteryBuffer[3][GTValues.V.length];
     public static final MetaTileEntityCharger[] CHARGER = new MetaTileEntityCharger[GTValues.V.length];
+
     // SIMPLE MACHINES SECTION
-    public static final SimpleMachineMetaTileEntity[] ELECTRIC_FURNACE = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] MACERATOR = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] ALLOY_SMELTER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] ARC_FURNACE = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] ASSEMBLER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] AUTOCLAVE = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
+    public static final SimpleMachineMetaTileEntity[] ELECTRIC_FURNACE = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] MACERATOR = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] ALLOY_SMELTER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] ARC_FURNACE = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] ASSEMBLER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] AUTOCLAVE = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] BENDER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] BREWERY = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] CANNER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
-    public static final SimpleMachineMetaTileEntity[] CENTRIFUGE = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] CHEMICAL_BATH = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] CHEMICAL_REACTOR = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] COMPRESSOR = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
+    public static final SimpleMachineMetaTileEntity[] CENTRIFUGE = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] CHEMICAL_BATH = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] CHEMICAL_REACTOR = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] COMPRESSOR = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] CUTTER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
-    public static final SimpleMachineMetaTileEntity[] DISTILLERY = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] ELECTROLYZER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] ELECTROMAGNETIC_SEPARATOR = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] EXTRACTOR = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
+    public static final SimpleMachineMetaTileEntity[] DISTILLERY = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] ELECTROLYZER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] ELECTROMAGNETIC_SEPARATOR = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] EXTRACTOR = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] EXTRUDER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
-    public static final SimpleMachineMetaTileEntity[] FERMENTER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] FLUID_HEATER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] FLUID_SOLIDIFIER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] FORGE_HAMMER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] FORMING_PRESS = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
+    public static final SimpleMachineMetaTileEntity[] FERMENTER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] FLUID_HEATER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] FLUID_SOLIDIFIER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] FORGE_HAMMER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] FORMING_PRESS = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] LATHE = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] MIXER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
-    public static final SimpleMachineMetaTileEntity[] ORE_WASHER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
+    public static final SimpleMachineMetaTileEntity[] ORE_WASHER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] PACKER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] UNPACKER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
-    public static final SimpleMachineMetaTileEntity[] POLARIZER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    public static final SimpleMachineMetaTileEntity[] LASER_ENGRAVER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
+    public static final SimpleMachineMetaTileEntity[] POLARIZER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    public static final SimpleMachineMetaTileEntity[] LASER_ENGRAVER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] SIFTER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
-    public static final SimpleMachineMetaTileEntity[] THERMAL_CENTRIFUGE = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
+    public static final SimpleMachineMetaTileEntity[] THERMAL_CENTRIFUGE = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
     public static final SimpleMachineMetaTileEntity[] WIREMILL = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
-    public static final SimpleMachineMetaTileEntity[] CIRCUIT_ASSEMBLER = new SimpleMachineMetaTileEntity[GTValues.V.length -
-            1];
-    // TODO Replication system
-    // public static final SimpleMachineMetaTileEntity[] MASS_FABRICATOR = new
-    // SimpleMachineMetaTileEntity[GTValues.V.length - 1];
-    // public static final SimpleMachineMetaTileEntity[] REPLICATOR = new SimpleMachineMetaTileEntity[GTValues.V.length
-    // - 1];
+    public static final SimpleMachineMetaTileEntity[] CIRCUIT_ASSEMBLER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
+    // public static final SimpleMachineMetaTileEntity[] MASS_FABRICATOR = new SimpleMachineMetaTileEntity[GTValues.V.length - 1]; // TODO Replication
+    // public static final SimpleMachineMetaTileEntity[] REPLICATOR = new SimpleMachineMetaTileEntity[GTValues.V.length - 1]; // TODO Replication
     public static final SimpleMachineMetaTileEntity[] SCANNER = new SimpleMachineMetaTileEntity[GTValues.V.length - 1];
-    public static final SimpleMachineMetaTileEntity[] GAS_COLLECTOR = new MetaTileEntityGasCollector[GTValues.V.length -
-            1];
+    public static final SimpleMachineMetaTileEntity[] GAS_COLLECTOR = new MetaTileEntityGasCollector[GTValues.V.length - 1];
     public static final MetaTileEntityRockBreaker[] ROCK_BREAKER = new MetaTileEntityRockBreaker[GTValues.V.length - 1];
     public static final MetaTileEntityMiner[] MINER = new MetaTileEntityMiner[GTValues.V.length - 1];
+
     // GENERATORS SECTION
     public static final SimpleGeneratorMetaTileEntity[] COMBUSTION_GENERATOR = new SimpleGeneratorMetaTileEntity[4];
     public static final SimpleGeneratorMetaTileEntity[] STEAM_TURBINE = new SimpleGeneratorMetaTileEntity[4];
     public static final SimpleGeneratorMetaTileEntity[] GAS_TURBINE = new SimpleGeneratorMetaTileEntity[4];
+
     // MULTIBLOCK PARTS SECTION
     public static final MetaTileEntityItemBus[] ITEM_IMPORT_BUS = new MetaTileEntityItemBus[GTValues.UHV + 1]; // ULV-UHV
     public static final MetaTileEntityItemBus[] ITEM_EXPORT_BUS = new MetaTileEntityItemBus[GTValues.UHV + 1];
@@ -234,40 +210,15 @@ public class MetaTileEntities {
     public static final MetaTileEntityMultiFluidHatch[] QUADRUPLE_EXPORT_HATCH = new MetaTileEntityMultiFluidHatch[6]; // EV-UHV
     public static final MetaTileEntityMultiFluidHatch[] NONUPLE_EXPORT_HATCH = new MetaTileEntityMultiFluidHatch[6]; // EV-UHV
     public static final MetaTileEntityEnergyHatch[] ENERGY_INPUT_HATCH = new MetaTileEntityEnergyHatch[GTValues.V.length];
-    public static final MetaTileEntityEnergyHatch[] ENERGY_INPUT_HATCH_4A = new MetaTileEntityEnergyHatch[6]; // EV, IV,
-                                                                                                              // LuV,
-                                                                                                              // ZPM,
-                                                                                                              // UV, UHV
-    public static final MetaTileEntityEnergyHatch[] ENERGY_INPUT_HATCH_16A = new MetaTileEntityEnergyHatch[5]; // IV,
-                                                                                                               // LuV,
-                                                                                                               // ZPM,
-                                                                                                               // UV,
-                                                                                                               // UHV
+    public static final MetaTileEntityEnergyHatch[] ENERGY_INPUT_HATCH_4A = new MetaTileEntityEnergyHatch[6]; // EV, IV, LuV, ZPM, UV, UHV
+    public static final MetaTileEntityEnergyHatch[] ENERGY_INPUT_HATCH_16A = new MetaTileEntityEnergyHatch[5]; // IV, LuV, ZPM, UV, UHV
     public static final MetaTileEntityEnergyHatch[] ENERGY_OUTPUT_HATCH = new MetaTileEntityEnergyHatch[GTValues.V.length];
-    public static final MetaTileEntityEnergyHatch[] ENERGY_OUTPUT_HATCH_4A = new MetaTileEntityEnergyHatch[6]; // EV,
-                                                                                                               // IV,
-                                                                                                               // LuV,
-                                                                                                               // ZPM,
-                                                                                                               // UV,
-                                                                                                               // UHV
-    public static final MetaTileEntityEnergyHatch[] ENERGY_OUTPUT_HATCH_16A = new MetaTileEntityEnergyHatch[5]; // IV,
-                                                                                                                // LuV,
-                                                                                                                // ZPM,
-                                                                                                                // UV,
-                                                                                                                // UHV
-    public static final MetaTileEntitySubstationEnergyHatch[] SUBSTATION_ENERGY_INPUT_HATCH = new MetaTileEntitySubstationEnergyHatch[5]; // IV,
-                                                                                                                                          // LuV,
-                                                                                                                                          // ZPM,
-                                                                                                                                          // UV,
-                                                                                                                                          // UHV
-    public static final MetaTileEntitySubstationEnergyHatch[] SUBSTATION_ENERGY_OUTPUT_HATCH = new MetaTileEntitySubstationEnergyHatch[5]; // IV,
-                                                                                                                                           // LuV,
-                                                                                                                                           // ZPM,
-                                                                                                                                           // UV,
-                                                                                                                                           // UHV
-    public static final MetaTileEntityRotorHolder[] ROTOR_HOLDER = new MetaTileEntityRotorHolder[6]; // HV, EV, IV, LuV,
-                                                                                                     // ZPM, UV
-    public static final MetaTileEntityMufflerHatch[] MUFFLER_HATCH = new MetaTileEntityMufflerHatch[GTValues.UV]; // LV-UV
+    public static final MetaTileEntityEnergyHatch[] ENERGY_OUTPUT_HATCH_4A = new MetaTileEntityEnergyHatch[6]; // EV, IV, LuV, ZPM, UV, UHV
+    public static final MetaTileEntityEnergyHatch[] ENERGY_OUTPUT_HATCH_16A = new MetaTileEntityEnergyHatch[5]; // IV, LuV, ZPM, UV, UHV
+    public static final MetaTileEntitySubstationEnergyHatch[] SUBSTATION_ENERGY_INPUT_HATCH = new MetaTileEntitySubstationEnergyHatch[5]; // IV, LuV, ZPM, UV, UHV
+    public static final MetaTileEntitySubstationEnergyHatch[] SUBSTATION_ENERGY_OUTPUT_HATCH = new MetaTileEntitySubstationEnergyHatch[5]; // IV, LuV, ZPM, UV, UHV
+    public static final MetaTileEntityRotorHolder[] ROTOR_HOLDER = new MetaTileEntityRotorHolder[6]; // HV, EV, IV, LuV, ZPM, UV
+    public static final MetaTileEntityMufflerHatch[] MUFFLER_HATCH = new MetaTileEntityMufflerHatch[GTValues.UV + 1]; // LV-UV
     public static final MetaTileEntityFusionReactor[] FUSION_REACTOR = new MetaTileEntityFusionReactor[3];
     public static final MetaTileEntityQuantumChest[] QUANTUM_CHEST = new MetaTileEntityQuantumChest[10];
     public static final MetaTileEntityQuantumTank[] QUANTUM_TANK = new MetaTileEntityQuantumTank[10];
@@ -305,6 +256,7 @@ public class MetaTileEntities {
     // Used for addons if they wish to disable certain tiers of machines
     private static final Map MID_TIER = new HashMap<>();
     private static final Map HIGH_TIER = new HashMap<>();
+
     // STEAM AGE SECTION
     public static SteamCoalBoiler STEAM_BOILER_COAL_BRONZE;
     public static SteamCoalBoiler STEAM_BOILER_COAL_STEEL;
@@ -338,6 +290,7 @@ public class MetaTileEntities {
     public static MetaTileEntityMaintenanceHatch CONFIGURABLE_MAINTENANCE_HATCH;
     public static MetaTileEntityAutoMaintenanceHatch AUTO_MAINTENANCE_HATCH;
     public static MetaTileEntityCleaningMaintenanceHatch CLEANING_MAINTENANCE_HATCH;
+
     // MULTIBLOCKS SECTION
     public static MetaTileEntityPrimitiveBlastFurnace PRIMITIVE_BLAST_FURNACE;
     public static MetaTileEntityCokeOven COKE_OVEN;
@@ -398,6 +351,7 @@ public class MetaTileEntities {
     public static MetaTileEntityCrate STAINLESS_STEEL_CRATE;
     public static MetaTileEntityCrate TITANIUM_CRATE;
     public static MetaTileEntityCrate TUNGSTENSTEEL_CRATE;
+
     // MISC MACHINES SECTION
     public static MetaTileEntityWorkbench WORKBENCH;
     public static MetaTileEntityCreativeEnergy CREATIVE_ENERGY;
@@ -410,12 +364,16 @@ public class MetaTileEntities {
     public static MetaTileEntity ITEM_EXPORT_BUS_ME;
     public static MetaTileEntity FLUID_IMPORT_HATCH_ME;
     public static MetaTileEntity ITEM_IMPORT_BUS_ME;
+    public static MetaTileEntity STOCKING_BUS_ME;
+    public static MetaTileEntity STOCKING_HATCH_ME;
     public static MetaTileEntityLDItemEndpoint LONG_DIST_ITEM_ENDPOINT;
     public static MetaTileEntityLDFluidEndpoint LONG_DIST_FLUID_ENDPOINT;
     public static MetaTileEntityAlarm ALARM;
 
     public static MetaTileEntityConverter[][] ENERGY_CONVERTER = new MetaTileEntityConverter[4][GTValues.V.length];
 
+    //spotless:on
+
     public static void init() {
         GTLog.logger.info("Registering MetaTileEntities");
 
@@ -1137,12 +1095,13 @@ public static void init() {
         CLEANING_MAINTENANCE_HATCH = registerMetaTileEntity(1401,
                 new MetaTileEntityCleaningMaintenanceHatch(gregtechId("maintenance_hatch_cleanroom_auto")));
 
-        // Muffler Hatches, IDs 1657-
-        for (int i = 0; i < MUFFLER_HATCH.length; i++) {
-            String voltageName = GTValues.VN[i + 1].toLowerCase();
-            MUFFLER_HATCH[i] = new MetaTileEntityMufflerHatch(gregtechId("muffler_hatch." + voltageName), i + 1);
+        // Muffler Hatches, IDs 1657-1664
+        for (int i = 0; i < MUFFLER_HATCH.length - 1; i++) {
+            int tier = i + 1;
+            String voltageName = GTValues.VN[tier].toLowerCase();
+            MUFFLER_HATCH[tier] = new MetaTileEntityMufflerHatch(gregtechId("muffler_hatch." + voltageName), tier);
 
-            registerMetaTileEntity(1657 + i, MUFFLER_HATCH[i]);
+            registerMetaTileEntity(1657 + i, MUFFLER_HATCH[tier]);
         }
 
         CLIPBOARD_TILE = registerMetaTileEntity(1666, new MetaTileEntityClipboard(gregtechId("clipboard")));
@@ -1167,8 +1126,8 @@ public static void init() {
         // IDs 1730-1744 are taken by 4A <-> 16A Transformers. They are grouped with other transformers for
         // organization.
 
-        // ME Hatches, IDs 1745-1748
-        if (Loader.isModLoaded(GTValues.MODID_APPENG)) {
+        // ME Hatches, IDs 1745-1748, 1752-1756
+        if (Mods.AppliedEnergistics2.isModLoaded()) {
             FLUID_EXPORT_HATCH_ME = registerMetaTileEntity(1745,
                     new MetaTileEntityMEOutputHatch(gregtechId("me_export_fluid_hatch")));
             ITEM_EXPORT_BUS_ME = registerMetaTileEntity(1746,
@@ -1177,6 +1136,13 @@ public static void init() {
                     new MetaTileEntityMEInputHatch(gregtechId("me_import_fluid_hatch")));
             ITEM_IMPORT_BUS_ME = registerMetaTileEntity(1748,
                     new MetaTileEntityMEInputBus(gregtechId("me_import_item_bus")));
+            STOCKING_BUS_ME = registerMetaTileEntity(1752,
+                    new MetaTileEntityMEStockingBus(gregtechId("me_stocking_item_bus")));
+            STOCKING_HATCH_ME = registerMetaTileEntity(1753,
+                    new MetaTileEntityMEStockingHatch(gregtechId("me_stocking_fluid_hatch")));
+            // 1754: Crafting Input Bus
+            // 1755: Crafting Input Buffer
+            // 1756: Crafting Input Slave
         }
 
         LONG_DIST_ITEM_ENDPOINT = registerMetaTileEntity(1749,
@@ -1187,6 +1153,8 @@ public static void init() {
         // Alarm, ID 1751
         ALARM = registerMetaTileEntity(1751, new MetaTileEntityAlarm(gregtechId("alarm")));
 
+        // IDs 1752-1756 are taken by AE2 parts
+
         // Multi-Fluid Hatches, IDs 1190, 1191, 1205, 1206, 1780-1799
         // EV hatches separate because of old names/IDs
         QUADRUPLE_IMPORT_HATCH[0] = registerMetaTileEntity(1190,
@@ -1289,7 +1257,7 @@ public static  T registerMetaTileEntity(int id, T samp
         if (sampleMetaTileEntity instanceof IMultiblockAbilityPart abilityPart) {
             MultiblockAbility.registerMultiblockAbility(abilityPart.getAbility(), sampleMetaTileEntity);
         }
-        if (sampleMetaTileEntity instanceof MultiblockControllerBase && Loader.isModLoaded(GTValues.MODID_JEI)) {
+        if (sampleMetaTileEntity instanceof MultiblockControllerBase && Mods.JustEnoughItems.isModLoaded()) {
             if (((MultiblockControllerBase) sampleMetaTileEntity).shouldShowInJei()) {
                 MultiblockInfoCategory.registerMultiblock((MultiblockControllerBase) sampleMetaTileEntity);
             }
diff --git a/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityAlarm.java b/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityAlarm.java
index cadf9768286..67751f893e9 100644
--- a/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityAlarm.java
+++ b/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityAlarm.java
@@ -76,15 +76,17 @@ protected ModularUI createUI(EntityPlayer entityPlayer) {
         return ModularUI.builder(GuiTextures.BACKGROUND, 240, 86)
                 .widget(new LabelWidget(10, 5, getMetaFullName()))
                 .widget(new SelectorWidget(10, 20, 220, 20,
-                        getSounds().stream().map((event) -> event.getSoundName().toString())
+                        getSounds().stream().map(this::getNameOfSound)
                                 .collect(Collectors.toList()),
-                        0x555555, () -> this.selectedSound.getSoundName().toString(), true).setOnChanged((v) -> {
-                            GregTechAPI.soundManager.stopTileSound(getPos());
+                        0x555555, () -> getNameOfSound(this.selectedSound), true).setOnChanged((v) -> {
+                            if (this.getWorld().isRemote)
+                                GregTechAPI.soundManager.stopTileSound(getPos());
                             SoundEvent newSound = SoundEvent.REGISTRY.getObject(new ResourceLocation(v));
                             if (this.selectedSound != newSound) {
                                 this.selectedSound = SoundEvent.REGISTRY.getObject(new ResourceLocation(v));
                                 this.writeCustomData(GregtechDataCodes.UPDATE_SOUND,
-                                        (writer) -> writer.writeResourceLocation(this.selectedSound.getSoundName()));
+                                        (writer) -> writer
+                                                .writeResourceLocation(getResourceLocationOfSound(this.selectedSound)));
                             }
                         }))
                 .widget(new ImageWidget(10, 54, 220, 20, GuiTextures.DISPLAY))
@@ -168,14 +170,14 @@ public void receiveInitialSyncData(PacketBuffer buf) {
     public void writeInitialSyncData(PacketBuffer buf) {
         super.writeInitialSyncData(buf);
         buf.writeBoolean(this.isActive);
-        buf.writeResourceLocation(this.selectedSound.getSoundName());
+        buf.writeResourceLocation(getResourceLocationOfSound(this.selectedSound));
         buf.writeInt(this.radius);
     }
 
     @Override
     public NBTTagCompound writeToNBT(NBTTagCompound data) {
         data.setBoolean("isActive", this.isActive);
-        data.setString("selectedSound", this.selectedSound.getSoundName().toString());
+        data.setString("selectedSound", getNameOfSound(this.selectedSound));
         data.setInteger("radius", this.radius);
         return super.writeToNBT(data);
     }
@@ -187,4 +189,12 @@ public void readFromNBT(NBTTagCompound data) {
         this.radius = data.getInteger("radius");
         super.readFromNBT(data);
     }
+
+    public String getNameOfSound(SoundEvent sound) {
+        return getResourceLocationOfSound(sound).toString();
+    }
+
+    public ResourceLocation getResourceLocationOfSound(SoundEvent sound) {
+        return SoundEvent.REGISTRY.getNameForObject(sound);
+    }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityGasCollector.java b/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityGasCollector.java
index 77bdd3b0485..8300d2b5fc9 100644
--- a/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityGasCollector.java
+++ b/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityGasCollector.java
@@ -1,24 +1,17 @@
 package gregtech.common.metatileentities.electric;
 
-import gregtech.api.capability.IEnergyContainer;
 import gregtech.api.capability.impl.RecipeLogicEnergy;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.SimpleMachineMetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
-import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMap;
 import gregtech.api.recipes.RecipeMaps;
-import gregtech.api.recipes.recipeproperties.GasCollectorDimensionProperty;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 
 import net.minecraft.util.ResourceLocation;
 
-import it.unimi.dsi.fastutil.ints.IntLists;
-import org.jetbrains.annotations.NotNull;
-
 import java.util.function.Function;
-import java.util.function.Supplier;
 
 public class MetaTileEntityGasCollector extends SimpleMachineMetaTileEntity {
 
@@ -36,28 +29,6 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
 
     @Override
     protected RecipeLogicEnergy createWorkable(RecipeMap recipeMap) {
-        return new GasCollectorRecipeLogic(this, recipeMap, () -> energyContainer);
-    }
-
-    protected boolean checkRecipe(@NotNull Recipe recipe) {
-        for (int dimension : recipe.getProperty(GasCollectorDimensionProperty.getInstance(), IntLists.EMPTY_LIST)) {
-            if (dimension == this.getWorld().provider.getDimension()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static class GasCollectorRecipeLogic extends RecipeLogicEnergy {
-
-        public GasCollectorRecipeLogic(MetaTileEntity metaTileEntity, RecipeMap recipeMap,
-                                       Supplier energyContainer) {
-            super(metaTileEntity, recipeMap, energyContainer);
-        }
-
-        @Override
-        public boolean checkRecipe(@NotNull Recipe recipe) {
-            return ((MetaTileEntityGasCollector) metaTileEntity).checkRecipe(recipe) && super.checkRecipe(recipe);
-        }
+        return new RecipeLogicEnergy(this, recipeMap, () -> energyContainer);
     }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityHull.java b/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityHull.java
index 926f7386174..f953dff1b4e 100644
--- a/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityHull.java
+++ b/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityHull.java
@@ -9,6 +9,7 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockAbilityPart;
 import gregtech.api.metatileentity.multiblock.IPassthroughHatch;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
+import gregtech.api.util.Mods;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.client.utils.PipelineUtil;
 import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityMultiblockPart;
@@ -19,7 +20,6 @@
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.world.World;
-import net.minecraftforge.fml.common.Loader;
 import net.minecraftforge.fml.common.Optional;
 
 import appeng.api.util.AECableType;
@@ -89,21 +89,21 @@ public void addInformation(ItemStack stack, @Nullable World player, List
     @Override
     public void update() {
         super.update();
-        if (isFirstTick() && Loader.isModLoaded(GTValues.MODID_APPENG)) {
+        if (isFirstTick() && Mods.AppliedEnergistics2.isModLoaded()) {
             if (getProxy() != null) getProxy().onReady();
         }
     }
 
     @NotNull
     @Override
-    @Optional.Method(modid = GTValues.MODID_APPENG)
+    @Optional.Method(modid = Mods.Names.APPLIED_ENERGISTICS2)
     public AECableType getCableConnectionType(@NotNull AEPartLocation part) {
         return AECableType.SMART;
     }
 
     @Nullable
     @Override
-    @Optional.Method(modid = GTValues.MODID_APPENG)
+    @Optional.Method(modid = Mods.Names.APPLIED_ENERGISTICS2)
     public AENetworkProxy getProxy() {
         if (gridProxy == null && getHolder() instanceof MetaTileEntityHolder) {
             gridProxy = new AENetworkProxy((MetaTileEntityHolder) getHolder(), "proxy", getStackForm(), true);
diff --git a/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityWorldAccelerator.java b/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityWorldAccelerator.java
index 09c851400ce..861cdd4c9e9 100644
--- a/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityWorldAccelerator.java
+++ b/src/main/java/gregtech/common/metatileentities/electric/MetaTileEntityWorldAccelerator.java
@@ -29,7 +29,6 @@
 import net.minecraft.util.text.TextComponentTranslation;
 import net.minecraft.world.World;
 import net.minecraftforge.common.capabilities.Capability;
-import net.minecraftforge.fml.common.FMLCommonHandler;
 
 import codechicken.lib.raytracer.CuboidRayTraceResult;
 import codechicken.lib.render.CCRenderState;
@@ -57,7 +56,6 @@ public class MetaTileEntityWorldAccelerator extends TieredMetaTileEntity impleme
     private boolean tileMode = false;
     private boolean isActive = false;
     private boolean isPaused = false;
-    private int lastTick;
 
     // Variables for Random Tick mode optimization
     // limit = ((tier - min) / (max - min)) * 2^tier
@@ -67,7 +65,6 @@ public class MetaTileEntityWorldAccelerator extends TieredMetaTileEntity impleme
 
     public MetaTileEntityWorldAccelerator(ResourceLocation metaTileEntityId, int tier) {
         super(metaTileEntityId, tier);
-        this.lastTick = 0;
         this.speed = (int) Math.pow(2, tier);
         this.successLimit = SUCCESS_LIMITS[tier - 1];
         initializeInventory();
@@ -124,6 +121,11 @@ protected long getTEModeAmperage() {
         return 6L;
     }
 
+    @Override
+    public boolean allowTickAcceleration() {
+        return false;
+    }
+
     @Override
     public void update() {
         super.update();
@@ -131,15 +133,11 @@ public void update() {
             if (isPaused && isActive) {
                 setActive(false);
             } else if (!isPaused) {
-                int currentTick = FMLCommonHandler.instance().getMinecraftServerInstance().getTickCounter();
-                if (currentTick != lastTick) { // Prevent other tick accelerators from accelerating us
-                    lastTick = currentTick;
-                    boolean wasSuccessful = isTEMode() ? handleTEMode() : handleRandomTickMode();
-                    if (!wasSuccessful) {
-                        setActive(false);
-                    } else if (!isActive) {
-                        setActive(true);
-                    }
+                boolean wasSuccessful = isTEMode() ? handleTEMode() : handleRandomTickMode();
+                if (!wasSuccessful) {
+                    setActive(false);
+                } else if (!isActive) {
+                    setActive(true);
                 }
             }
         }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index b9fd8ebf60c..c09b9b91203 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -1,17 +1,34 @@
 package gregtech.common.metatileentities.multi.electric;
 
 import gregtech.api.GTValues;
-import gregtech.api.capability.*;
+import gregtech.api.capability.GregtechDataCodes;
+import gregtech.api.capability.GregtechTileCapabilities;
+import gregtech.api.capability.IEnergyContainer;
+import gregtech.api.capability.IMufflerHatch;
+import gregtech.api.capability.IWorkable;
 import gregtech.api.capability.impl.CleanroomLogic;
 import gregtech.api.capability.impl.EnergyContainerList;
 import gregtech.api.metatileentity.IDataInfoProvider;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.SimpleGeneratorMetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
-import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.*;
+import gregtech.api.metatileentity.multiblock.CleanroomType;
+import gregtech.api.metatileentity.multiblock.FuelMultiblockController;
+import gregtech.api.metatileentity.multiblock.ICleanroomProvider;
+import gregtech.api.metatileentity.multiblock.ICleanroomReceiver;
+import gregtech.api.metatileentity.multiblock.IMultiblockPart;
+import gregtech.api.metatileentity.multiblock.MultiblockAbility;
+import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
+import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
+import gregtech.api.pattern.BlockPattern;
+import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.MultiblockShapeInfo;
+import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.pattern.PatternStringError;
+import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.Mods;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
@@ -48,7 +65,6 @@
 import net.minecraft.util.text.TextFormatting;
 import net.minecraft.world.World;
 import net.minecraftforge.common.capabilities.Capability;
-import net.minecraftforge.fml.common.Loader;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
@@ -61,7 +77,13 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
 
 public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
                                      implements ICleanroomProvider, IWorkable, IDataInfoProvider {
@@ -521,7 +543,7 @@ public void addInformation(ItemStack stack, @Nullable World player, List
             tooltip.add(I18n.format("gregtech.machine.cleanroom.tooltip.7"));
             tooltip.add(I18n.format("gregtech.machine.cleanroom.tooltip.8"));
             tooltip.add(I18n.format("gregtech.machine.cleanroom.tooltip.9"));
-            if (Loader.isModLoaded(GTValues.MODID_APPENG)) {
+            if (Mods.AppliedEnergistics2.isModLoaded()) {
                 tooltip.add(I18n.format(AEConfig.instance().isFeatureEnabled(AEFeature.CHANNELS) ?
                         "gregtech.machine.cleanroom.tooltip.ae2.channels" :
                         "gregtech.machine.cleanroom.tooltip.ae2.no_channels"));
@@ -643,6 +665,8 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
         if (dataId == GregtechDataCodes.UPDATE_STRUCTURE_SIZE) {
             this.lDist = buf.readInt();
             this.rDist = buf.readInt();
+            this.bDist = buf.readInt();
+            this.fDist = buf.readInt();
             this.hDist = buf.readInt();
         } else if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) {
             this.cleanroomLogic.setActive(buf.readBoolean());
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
index a1501dd6f65..dca07a39e5f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
@@ -184,7 +184,7 @@ protected void addDisplayText(List textList) {
                                     TextFormatting.BLUE,
                                     TextFormattingUtil.formatNumbers(
                                             minerLogic.getFluidToProduce() * 20L / FluidDrillLogic.MAX_PROGRESS) +
-                                            " L/t");
+                                            " L/s");
                             tl.add(TextComponentUtil.translationWithColor(
                                     TextFormatting.GRAY,
                                     "gregtech.multiblock.fluid_rig.fluid_amount",
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index 8e667bedfcb..35c06785bab 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -30,6 +30,7 @@
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.recipeproperties.FusionEUToStartProperty;
+import gregtech.api.recipes.recipeproperties.IRecipePropertyStorage;
 import gregtech.api.util.RelativeDirection;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
@@ -630,6 +631,19 @@ public boolean checkRecipe(@NotNull Recipe recipe) {
             return true;
         }
 
+        @Override
+        protected void modifyOverclockPre(int @NotNull [] values, @NotNull IRecipePropertyStorage storage) {
+            super.modifyOverclockPre(values, storage);
+
+            // Limit the number of OCs to the difference in fusion reactor MK.
+            // I.e., a MK2 reactor can overclock a MK1 recipe once, and a
+            // MK3 reactor can overclock a MK2 recipe once, or a MK1 recipe twice.
+            long euToStart = storage.getRecipePropertyValue(FusionEUToStartProperty.getInstance(), 0L);
+            int fusionTier = FusionEUToStartProperty.getFusionTier(euToStart);
+            if (fusionTier != 0) fusionTier = MetaTileEntityFusionReactor.this.tier - fusionTier;
+            values[2] = Math.min(fusionTier, values[2]);
+        }
+
         @NotNull
         @Override
         public NBTTagCompound serializeNBT() {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index c6ee87449d2..7814f3dccc9 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -81,8 +81,10 @@ public class MetaTileEntityPowerSubstation extends MultiblockWithDisplayBase
     private boolean isActive, isWorkingEnabled = true;
 
     // Stats tracked for UI display
-    private long netIOLastSec;
-    private long averageIOLastSec;
+    private long netInLastSec;
+    private long averageInLastSec;
+    private long netOutLastSec;
+    private long averageOutLastSec;
 
     public MetaTileEntityPowerSubstation(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId);
@@ -138,8 +140,10 @@ public void invalidateStructure() {
         inputHatches = null;
         outputHatches = null;
         passiveDrain = 0;
-        netIOLastSec = 0;
-        averageIOLastSec = 0;
+        netInLastSec = 0;
+        averageInLastSec = 0;
+        netOutLastSec = 0;
+        averageOutLastSec = 0;
         super.invalidateStructure();
     }
 
@@ -149,25 +153,27 @@ protected void updateFormedValid() {
             if (getOffsetTimer() % 20 == 0) {
                 // active here is just used for rendering
                 setActive(energyBank.hasEnergy());
-                averageIOLastSec = netIOLastSec / 20;
-                netIOLastSec = 0;
+                averageInLastSec = netInLastSec / 20;
+                averageOutLastSec = netOutLastSec / 20;
+                netInLastSec = 0;
+                netOutLastSec = 0;
             }
 
             if (isWorkingEnabled()) {
                 // Bank from Energy Input Hatches
                 long energyBanked = energyBank.fill(inputHatches.getEnergyStored());
                 inputHatches.changeEnergy(-energyBanked);
-                netIOLastSec += energyBanked;
+                netInLastSec += energyBanked;
 
                 // Passive drain
                 long energyPassiveDrained = energyBank.drain(getPassiveDrain());
-                netIOLastSec -= energyPassiveDrained;
+                netOutLastSec += energyPassiveDrained;
 
                 // Debank to Dynamo Hatches
                 long energyDebanked = energyBank
                         .drain(outputHatches.getEnergyCapacity() - outputHatches.getEnergyStored());
                 outputHatches.changeEnergy(energyDebanked);
-                netIOLastSec -= energyDebanked;
+                netOutLastSec += energyDebanked;
             }
         }
     }
@@ -359,44 +365,45 @@ protected void addDisplayText(List textList) {
                                 "gregtech.multiblock.power_substation.passive_drain",
                                 passiveDrain));
 
-                        // Average I/O line
-                        TextFormatting averageIOColor = TextFormatting.GRAY;
-                        if (isActive() && isWorkingEnabled() && averageIOLastSec == 0) {
-                            // only set to yellow on zero if the machine is on, avoids a yellow "warning"
-                            // color when the machine is first formed and not yet plugged in.
-                            averageIOColor = TextFormatting.YELLOW;
-                        } else if (averageIOLastSec > 0) {
-                            averageIOColor = TextFormatting.GREEN;
-                        } else if (averageIOLastSec < 0) {
-                            averageIOColor = TextFormatting.RED;
-                        }
-
-                        ITextComponent averageIO = TextComponentUtil.stringWithColor(
-                                averageIOColor,
-                                TextFormattingUtil.formatNumbers(averageIOLastSec) + " EU/t");
-
+                        // Average EU IN line
+                        ITextComponent avgValue = TextComponentUtil.stringWithColor(
+                                TextFormatting.GREEN,
+                                TextFormattingUtil.formatNumbers(averageInLastSec) + " EU/t");
                         ITextComponent base = TextComponentUtil.translationWithColor(
                                 TextFormatting.GRAY,
-                                "gregtech.multiblock.power_substation.average_io",
-                                averageIO);
-
+                                "gregtech.multiblock.power_substation.average_in",
+                                avgValue);
                         ITextComponent hover = TextComponentUtil.translationWithColor(
                                 TextFormatting.GRAY,
-                                "gregtech.multiblock.power_substation.average_io_hover");
+                                "gregtech.multiblock.power_substation.average_in_hover");
+                        tl.add(TextComponentUtil.setHover(base, hover));
+
+                        // Average EU OUT line
+                        avgValue = TextComponentUtil.stringWithColor(
+                                TextFormatting.RED,
+                                TextFormattingUtil.formatNumbers(averageOutLastSec) + " EU/t");
+                        base = TextComponentUtil.translationWithColor(
+                                TextFormatting.GRAY,
+                                "gregtech.multiblock.power_substation.average_out",
+                                avgValue);
+                        hover = TextComponentUtil.translationWithColor(
+                                TextFormatting.GRAY,
+                                "gregtech.multiblock.power_substation.average_out_hover");
                         tl.add(TextComponentUtil.setHover(base, hover));
 
                         // Time to fill/drain line
-                        if (averageIOLastSec > 0) {
+                        if (averageInLastSec > averageOutLastSec) {
                             ITextComponent timeToFill = getTimeToFillDrainText(energyCapacity.subtract(energyStored)
-                                    .divide(BigInteger.valueOf(averageIOLastSec * 20)));
+                                    .divide(BigInteger.valueOf((averageInLastSec - averageOutLastSec) * 20)));
                             TextComponentUtil.setColor(timeToFill, TextFormatting.GREEN);
                             tl.add(TextComponentUtil.translationWithColor(
                                     TextFormatting.GRAY,
                                     "gregtech.multiblock.power_substation.time_to_fill",
                                     timeToFill));
-                        } else if (averageIOLastSec < 0) {
+                        } else if (averageInLastSec < averageOutLastSec) {
                             ITextComponent timeToDrain = getTimeToFillDrainText(
-                                    energyStored.divide(BigInteger.valueOf(Math.abs(averageIOLastSec) * 20)));
+                                    energyStored.divide(BigInteger.valueOf(
+                                            (averageOutLastSec - averageInLastSec) * 20)));
                             TextComponentUtil.setColor(timeToDrain, TextFormatting.RED);
                             tl.add(TextComponentUtil.translationWithColor(
                                     TextFormatting.GRAY,
@@ -412,9 +419,9 @@ protected void addDisplayText(List textList) {
     protected void addWarningText(List textList) {
         super.addWarningText(textList);
         if (isStructureFormed()) {
-            if (averageIOLastSec < 0) { // decreasing
+            if (averageInLastSec < averageOutLastSec) { // decreasing
                 BigInteger timeToDrainSeconds = energyBank.getStored()
-                        .divide(BigInteger.valueOf(Math.abs(averageIOLastSec) * 20));
+                        .divide(BigInteger.valueOf((averageOutLastSec - averageInLastSec) * 20));
                 if (timeToDrainSeconds.compareTo(BigInteger.valueOf(60 * 60)) < 0) { // less than 1 hour left
                     textList.add(TextComponentUtil.translationWithColor(
                             TextFormatting.YELLOW,
@@ -550,8 +557,12 @@ public String getCapacity() {
         return TextFormattingUtil.formatNumbers(energyBank.getCapacity());
     }
 
-    public long getAverageIOLastSec() {
-        return averageIOLastSec;
+    public long getAverageInLastSec() {
+        return averageInLastSec;
+    }
+
+    public long getAverageOutLastSec() {
+        return averageOutLastSec;
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java
index a5d37f33c41..46bcafb51d5 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java
@@ -202,20 +202,69 @@ private void updateProxyPlugin() {
     }
 
     public int getX() {
-        if (this.getController() != null) {
-            if (this.getController().getPos().getX() - this.getPos().getX() != 0) {
-                return Math.abs(this.getController().getPos().getX() - this.getPos().getX()) - 1;
-            } else {
-                return Math.abs(this.getController().getPos().getZ() - this.getPos().getZ()) - 1;
+        MultiblockControllerBase controller = this.getController();
+        if (controller != null) {
+            EnumFacing spin = controller.getUpwardsFacing();
+            switch (controller.getFrontFacing().getAxis()) {
+                case Y -> {
+                    if (spin.getAxis() == EnumFacing.Axis.X)
+                        return Math.abs(this.getController().getPos().getZ() - this.getPos().getZ()) - 1;
+                    else
+                        return Math.abs(this.getController().getPos().getX() - this.getPos().getX()) - 1;
+                }
+                case X -> {
+                    if (spin.getAxis() == EnumFacing.Axis.Z)
+                        return Math.abs(this.getController().getPos().getZ() - this.getPos().getZ()) - 1;
+                    else
+                        return Math.abs(this.getController().getPos().getY() - this.getPos().getY()) - 1;
+                }
+                default -> {
+                    if (spin.getAxis() == EnumFacing.Axis.Z)
+                        return Math.abs(this.getController().getPos().getX() - this.getPos().getX()) - 1;
+                    else
+                        return Math.abs(this.getController().getPos().getY() - this.getPos().getY()) - 1;
+                }
             }
         }
         return -1;
     }
 
     public int getY() {
-        if (this.getController() != null) {
-            return ((MetaTileEntityCentralMonitor) this.getController()).height -
-                    (this.getPos().getY() + 1 - this.getController().getPos().getY()) - 1;
+        MultiblockControllerBase controller = this.getController();
+        if (controller != null) {
+            EnumFacing spin = controller.getUpwardsFacing();
+            EnumFacing facing = controller.getFrontFacing();
+            int height = ((MetaTileEntityCentralMonitor) this.getController()).height;
+            switch (facing.getAxis()) {
+                case Y -> {
+                    if (spin.getAxis() == EnumFacing.Axis.X)
+                        return height -
+                                (Math.abs(controller.getPos().getX() - spin.getXOffset() - this.getPos().getX())) - 1;
+                    else
+                        return height -
+                                (Math.abs(controller.getPos().getZ() - spin.getZOffset() - this.getPos().getZ())) - 1;
+                }
+                case X -> {
+                    if (spin.getAxis() == EnumFacing.Axis.Z)
+                        return height -
+                                (Math.abs(controller.getPos().getY() + spin.getZOffset() - this.getPos().getY())) - 1;
+                    else
+                        return height - (Math.abs(
+                                controller.getPos().getZ() + spin.getXOffset() * facing.rotateY().getZOffset() -
+                                        this.getPos().getZ())) -
+                                1;
+                }
+                default -> {
+                    if (spin.getAxis() == EnumFacing.Axis.Z)
+                        return height -
+                                (Math.abs(controller.getPos().getY() + spin.getZOffset() - this.getPos().getY())) - 1;
+                    else
+                        return height - (Math.abs(
+                                controller.getPos().getX() + spin.getXOffset() * facing.rotateY().getXOffset() -
+                                        this.getPos().getX())) -
+                                1;
+                }
+            }
         }
         return -1;
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/LargeTurbineWorkableHandler.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/LargeTurbineWorkableHandler.java
index 482d6c710b5..f9f393216e6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/LargeTurbineWorkableHandler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/LargeTurbineWorkableHandler.java
@@ -1,6 +1,7 @@
 package gregtech.common.metatileentities.multi.electric.generator;
 
 import gregtech.api.GTValues;
+import gregtech.api.capability.IMultipleTankHandler;
 import gregtech.api.capability.IRotorHolder;
 import gregtech.api.capability.impl.MultiblockFuelRecipeLogic;
 import gregtech.api.metatileentity.multiblock.FuelMultiblockController;
@@ -8,13 +9,20 @@
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeBuilder;
+import gregtech.api.recipes.RecipeMap;
+import gregtech.api.util.GTUtility;
 
+import net.minecraft.item.ItemStack;
 import net.minecraft.util.math.MathHelper;
 import net.minecraftforge.fluids.FluidStack;
 import net.minecraftforge.fluids.IFluidTank;
 import net.minecraftforge.fluids.capability.IFluidHandler;
+import net.minecraftforge.items.IItemHandlerModifiable;
+
+import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class LargeTurbineWorkableHandler extends MultiblockFuelRecipeLogic {
 
@@ -42,7 +50,9 @@ protected void updateRecipeProgress() {
     public FluidStack getInputFluidStack() {
         // Previous Recipe is always null on first world load, so try to acquire a new recipe
         if (previousRecipe == null) {
-            Recipe recipe = findRecipe(Integer.MAX_VALUE, getInputInventory(), getInputTank());
+            // previousRecipe is set whenever a valid recipe is found
+            // if it's not set, find *any* recipe we have at least the base (non-parallel) inputs for
+            Recipe recipe = super.findRecipe(Integer.MAX_VALUE, getInputInventory(), getInputTank());
 
             return recipe == null ? null : getInputTank().drain(
                     new FluidStack(recipe.getFluidInputs().get(0).getInputFluidStack().getFluid(), Integer.MAX_VALUE),
@@ -73,8 +83,55 @@ protected long boostProduction(long production) {
         return 0;
     }
 
+    private int getParallel(Recipe recipe, double totalHolderEfficiencyCoefficient, int turbineMaxVoltage) {
+        return MathHelper.ceil((turbineMaxVoltage - this.excessVoltage) /
+                (Math.abs(recipe.getEUt()) * totalHolderEfficiencyCoefficient));
+    }
+
+    private boolean canDoRecipeWithParallel(Recipe recipe) {
+        IRotorHolder rotorHolder = ((MetaTileEntityLargeTurbine) metaTileEntity).getRotorHolder();
+        if (rotorHolder == null || !rotorHolder.hasRotor())
+            return false;
+        double totalHolderEfficiencyCoefficient = rotorHolder.getTotalEfficiency() / 100.0;
+        int turbineMaxVoltage = (int) getMaxVoltage();
+        int parallel = getParallel(recipe, totalHolderEfficiencyCoefficient, turbineMaxVoltage);
+
+        FluidStack recipeFluidStack = recipe.getFluidInputs().get(0).getInputFluidStack();
+
+        // Intentionally not using this.getInputFluidStack because that is locked to the previous recipe
+        FluidStack inputFluid = getInputTank().drain(
+                new FluidStack(recipeFluidStack.getFluid(), Integer.MAX_VALUE),
+                false);
+        return inputFluid != null && inputFluid.amount >= recipeFluidStack.amount * parallel;
+    }
+
+    @Override
+    protected boolean checkPreviousRecipe() {
+        return super.checkPreviousRecipe() && canDoRecipeWithParallel(this.previousRecipe);
+    }
+
+    @Override
+    protected @Nullable Recipe findRecipe(long maxVoltage, IItemHandlerModifiable inputs,
+                                          IMultipleTankHandler fluidInputs) {
+        RecipeMap map = getRecipeMap();
+        if (map == null || !isRecipeMapValid(map)) {
+            return null;
+        }
+
+        final List items = GTUtility.itemHandlerToList(inputs).stream().filter(s -> !s.isEmpty()).collect(
+                Collectors.toList());
+        final List fluids = GTUtility.fluidHandlerToList(fluidInputs).stream()
+                .filter(f -> f != null && f.amount != 0)
+                .collect(Collectors.toList());
+
+        return map.find(items, fluids, recipe -> {
+            if (recipe.getEUt() > maxVoltage) return false;
+            return recipe.matches(false, inputs, fluidInputs) && this.canDoRecipeWithParallel(recipe);
+        });
+    }
+
     @Override
-    protected boolean prepareRecipe(Recipe recipe) {
+    public boolean prepareRecipe(Recipe recipe) {
         IRotorHolder rotorHolder = ((MetaTileEntityLargeTurbine) metaTileEntity).getRotorHolder();
         if (rotorHolder == null || !rotorHolder.hasRotor())
             return false;
@@ -83,13 +140,12 @@ protected boolean prepareRecipe(Recipe recipe) {
         FluidStack recipeFluidStack = recipe.getFluidInputs().get(0).getInputFluidStack();
         int parallel = 0;
 
-        if (excessVoltage >= turbineMaxVoltage) {
-            excessVoltage -= turbineMaxVoltage;
+        if (this.excessVoltage >= turbineMaxVoltage) {
+            this.excessVoltage -= turbineMaxVoltage;
         } else {
             double holderEfficiency = rotorHolder.getTotalEfficiency() / 100.0;
             // get the amount of parallel required to match the desired output voltage
-            parallel = MathHelper.ceil((turbineMaxVoltage - excessVoltage) /
-                    (Math.abs(recipe.getEUt()) * holderEfficiency));
+            parallel = getParallel(recipe, holderEfficiency, turbineMaxVoltage);
 
             // Null check fluid here, since it can return null on first join into world or first form
             FluidStack inputFluid = getInputFluidStack();
@@ -98,7 +154,7 @@ protected boolean prepareRecipe(Recipe recipe) {
             }
 
             // this is necessary to prevent over-consumption of fuel
-            excessVoltage += (int) (parallel * Math.abs(recipe.getEUt()) * holderEfficiency - turbineMaxVoltage);
+            this.excessVoltage += (int) (parallel * Math.abs(recipe.getEUt()) * holderEfficiency - turbineMaxVoltage);
         }
 
         // rebuild the recipe and adjust voltage to match the turbine
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
index 4b298f25cd0..7d58be463fe 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
@@ -16,6 +16,7 @@
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.unification.material.Materials;
+import gregtech.api.util.RelativeDirection;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
 import gregtech.client.renderer.ICubeRenderer;
@@ -28,7 +29,6 @@
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.resources.I18n;
 import net.minecraft.item.ItemStack;
-import net.minecraft.util.EnumFacing;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.text.ITextComponent;
@@ -192,18 +192,19 @@ protected void formStructure(PatternMatchContext context) {
     }
 
     private boolean checkIntakesObstructed() {
-        EnumFacing facing = this.getFrontFacing();
-        boolean permuteXZ = facing.getAxis() == EnumFacing.Axis.Z;
-        BlockPos centerPos = this.getPos().offset(facing);
-        for (int x = -1; x < 2; x++) {
-            for (int y = -1; y < 2; y++) {
-                // Skip the controller block itself
-                if (x == 0 && y == 0)
+        for (int left = -1; left <= 1; left++) {
+            for (int up = -1; up <= 1; up++) {
+                if (left == 0 && up == 0) {
+                    // Skip the controller block itself
                     continue;
-                BlockPos blockPos = centerPos.add(permuteXZ ? x : 0, y, permuteXZ ? 0 : x);
-                IBlockState blockState = this.getWorld().getBlockState(blockPos);
-                if (!blockState.getBlock().isAir(blockState, this.getWorld(), blockPos))
+                }
+
+                final BlockPos checkPos = RelativeDirection.offsetPos(
+                        getPos(), getFrontFacing(), getUpwardsFacing(), isFlipped(), up, left, 1);
+                final IBlockState state = getWorld().getBlockState(checkPos);
+                if (!state.getBlock().isAir(state, getWorld(), checkPos)) {
                     return true;
+                }
             }
         }
         return false;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java
index 850ea3730be..ae41352ceb2 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java
@@ -13,10 +13,13 @@
 import gregtech.client.renderer.texture.Textures;
 import gregtech.common.pipelike.optical.tile.TileEntityOpticalPipe;
 
+import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.ResourceLocation;
+import net.minecraft.world.World;
 import net.minecraftforge.common.capabilities.Capability;
 
 import codechicken.lib.render.CCRenderState;
@@ -166,4 +169,11 @@ public  T getCapability(Capability capability, EnumFacing side) {
         }
         return super.getCapability(capability, side);
     }
+
+    @Override
+    public void addInformation(ItemStack stack, @Nullable World world, @NotNull List tooltip,
+                               boolean advanced) {
+        super.addInformation(stack, world, tooltip, advanced);
+        tooltip.add(I18n.format("gregtech.universal.disabled"));
+    }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java
index c925716144e..a50acc06ba0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java
@@ -175,6 +175,11 @@ public void addInformation(ItemStack stack, @Nullable World world, @NotNull List
         } else {
             tooltip.add(I18n.format("gregtech.machine.data_access_hatch.tooltip.2", getInventorySize()));
         }
+        if (canPartShare()) {
+            tooltip.add(I18n.format("gregtech.universal.enabled"));
+        } else {
+            tooltip.add(I18n.format("gregtech.universal.disabled"));
+        }
     }
 
     @NotNull
@@ -199,7 +204,7 @@ public List getDataInfo() {
 
     @Override
     public boolean canPartShare() {
-        return false;
+        return isCreative;
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
index 2b5ffaddc4d..949f47b77ca 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
@@ -37,7 +37,7 @@
 import codechicken.lib.vec.Matrix4;
 import com.cleanroommc.modularui.api.drawable.IKey;
 import com.cleanroommc.modularui.api.widget.IWidget;
-import com.cleanroommc.modularui.manager.GuiCreationContext;
+import com.cleanroommc.modularui.factory.PosGuiData;
 import com.cleanroommc.modularui.screen.ModularPanel;
 import com.cleanroommc.modularui.value.BoolValue;
 import com.cleanroommc.modularui.value.sync.BooleanSyncValue;
@@ -265,8 +265,7 @@ public boolean usesMui2() {
     }
 
     @Override
-    public ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManager guiSyncManager,
-                                boolean isClient) {
+    public ModularPanel buildUI(PosGuiData guiData, GuiSyncManager guiSyncManager) {
         int rowSize = (int) Math.sqrt(getInventorySize());
         guiSyncManager.registerSlotGroup("item_inv", rowSize);
 
@@ -294,28 +293,6 @@ public ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManage
 
         boolean hasGhostCircuit = hasGhostCircuitInventory() && this.circuitInventory != null;
 
-        ToggleButton workingButton = new ToggleButton();
-        workingButton.value(
-                new BoolValue.Dynamic(workingStateValue::getBoolValue, val -> {
-                    workingStateValue.setBoolValue(val);
-                    workingButton.markTooltipDirty();
-                }))
-                .overlay(GTGuiTextures.BUTTON_ITEM_OUTPUT)
-                .tooltipBuilder(t -> t.addLine(workingStateValue.getBoolValue() ?
-                        IKey.lang("gregtech.gui.item_auto_output.tooltip.enabled") :
-                        IKey.lang("gregtech.gui.item_auto_output.tooltip.disabled")));
-
-        ToggleButton collapseButton = new ToggleButton();
-        collapseButton.value(
-                new BoolValue.Dynamic(collapseStateValue::getBoolValue, val -> {
-                    collapseStateValue.setBoolValue(val);
-                    collapseButton.markTooltipDirty();
-                }))
-                .overlay(GTGuiTextures.BUTTON_AUTO_COLLAPSE)
-                .tooltipBuilder(t -> t.addLine(collapseStateValue.getBoolValue() ?
-                        IKey.lang("gregtech.gui.item_auto_collapse.tooltip.enabled") :
-                        IKey.lang("gregtech.gui.item_auto_collapse.tooltip.disabled")));
-
         return GTGuis.createPanel(this, backgroundWidth, backgroundHeight)
                 .child(IKey.lang(getMetaFullName()).asWidget().pos(5, 5))
                 .child(SlotGroupWidget.playerInventory().left(7).bottom(7))
@@ -328,9 +305,25 @@ public ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManage
                 .child(new Column()
                         .pos(backgroundWidth - 7 - 18, backgroundHeight - 18 * 4 - 7 - 5)
                         .width(18).height(18 * 4 + 5)
-                        .child(GTGuiTextures.getLogo().asWidget().size(17).top(18 * 3 + 5))
-                        .child(workingButton.top(18 * 2))
-                        .child(collapseButton.top(18))
+                        .child(GTGuiTextures.getLogo(getUITheme()).asWidget().size(17).top(18 * 3 + 5))
+                        .child(new ToggleButton()
+                                .top(18 * 2)
+                                .value(new BoolValue.Dynamic(workingStateValue::getBoolValue,
+                                        workingStateValue::setBoolValue))
+                                .overlay(GTGuiTextures.BUTTON_ITEM_OUTPUT)
+                                .tooltipBuilder(t -> t.setAutoUpdate(true)
+                                        .addLine(workingStateValue.getBoolValue() ?
+                                                IKey.lang("gregtech.gui.item_auto_output.tooltip.enabled") :
+                                                IKey.lang("gregtech.gui.item_auto_output.tooltip.disabled"))))
+                        .child(new ToggleButton()
+                                .top(18)
+                                .value(new BoolValue.Dynamic(collapseStateValue::getBoolValue,
+                                        collapseStateValue::setBoolValue))
+                                .overlay(GTGuiTextures.BUTTON_AUTO_COLLAPSE)
+                                .tooltipBuilder(t -> t.setAutoUpdate(true)
+                                        .addLine(collapseStateValue.getBoolValue() ?
+                                                IKey.lang("gregtech.gui.item_auto_collapse.tooltip.enabled") :
+                                                IKey.lang("gregtech.gui.item_auto_collapse.tooltip.disabled"))))
                         .childIf(hasGhostCircuit, new GhostCircuitSlotWidget()
                                 .slot(SyncHandlers.itemSlot(circuitInventory, 0))
                                 .background(GTGuiTextures.SLOT, GTGuiTextures.INT_CIRCUIT_OVERLAY))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityLaserHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityLaserHatch.java
index 2f622588c7b..76acce75c33 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityLaserHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityLaserHatch.java
@@ -72,11 +72,6 @@ protected boolean openGUIOnRightClick() {
         return false;
     }
 
-    @Override
-    public boolean canPartShare() {
-        return false;
-    }
-
     @Override
     public MultiblockAbility getAbility() {
         return isOutput ? MultiblockAbility.OUTPUT_LASER : MultiblockAbility.INPUT_LASER;
@@ -114,7 +109,7 @@ public void addInformation(ItemStack stack, @Nullable World world, @NotNull List
             tooltip.add(I18n.format("gregtech.universal.tooltip.amperage_in_till", amperage));
         }
         tooltip.add(I18n.format("gregtech.universal.tooltip.energy_storage_capacity", buffer.getEnergyCapacity()));
-        tooltip.add(I18n.format("gregtech.universal.disabled"));
+        tooltip.add(I18n.format("gregtech.universal.enabled"));
     }
 
     @NotNull
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMufflerHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMufflerHatch.java
index 4b3e75a4c2c..5bc07d2e793 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMufflerHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMufflerHatch.java
@@ -11,6 +11,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockAbilityPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
 import gregtech.api.util.GTTransferUtils;
 import gregtech.api.util.GTUtility;
@@ -69,7 +70,7 @@ public void update() {
 
         if (getWorld().isRemote && getController() instanceof MultiblockWithDisplayBase controller &&
                 controller.isActive()) {
-            VanillaParticleEffects.MUFFLER_SMOKE.runEffect(this);
+            VanillaParticleEffects.mufflerEffect(this, controller.getMufflerParticle());
         }
     }
 
@@ -112,12 +113,15 @@ private boolean checkFrontFaceFree() {
         return blockState.getBlock().isAir(blockState, getWorld(), frontPos) || GTUtility.isBlockSnow(blockState);
     }
 
-    /** @deprecated Use {@link VanillaParticleEffects#MUFFLER_SMOKE} instead. */
+    /** @deprecated No longer needed. Multiblock controller sets the particle type. */
     @Deprecated
     @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
     @SideOnly(Side.CLIENT)
     public void pollutionParticles() {
-        VanillaParticleEffects.MUFFLER_SMOKE.runEffect(this);
+        MultiblockControllerBase controller = getController();
+        if (controller instanceof MultiblockWithDisplayBase displayBase) {
+            VanillaParticleEffects.mufflerEffect(this, displayBase.getMufflerParticle());
+        }
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiFluidHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiFluidHatch.java
index cf77abc3bb6..cf053eb8500 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiFluidHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiFluidHatch.java
@@ -122,6 +122,14 @@ public void receiveInitialSyncData(PacketBuffer buf) {
         this.workingEnabled = buf.readBoolean();
     }
 
+    @Override
+    public void receiveCustomData(int dataId, PacketBuffer buf) {
+        super.receiveCustomData(dataId, buf);
+        if (dataId == GregtechDataCodes.WORKING_ENABLED) {
+            this.workingEnabled = buf.readBoolean();
+        }
+    }
+
     @Override
     public NBTTagCompound writeToNBT(NBTTagCompound data) {
         super.writeToNBT(data);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
index 5658841fcda..efb526a0616 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
@@ -18,6 +18,7 @@
 import gregtech.client.renderer.texture.Textures;
 import gregtech.client.renderer.texture.cube.SimpleOverlayRenderer;
 
+import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
@@ -25,12 +26,14 @@
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.NonNullList;
 import net.minecraft.util.ResourceLocation;
+import net.minecraft.world.World;
 import net.minecraftforge.items.IItemHandler;
 
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
 
@@ -207,6 +210,18 @@ public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
         heldItems.removeNotifiableMetaTileEntity(controllerBase);
     }
 
+    @Override
+    public boolean canPartShare() {
+        return false;
+    }
+
+    @Override
+    public void addInformation(ItemStack stack, @Nullable World world, @NotNull List tooltip,
+                               boolean advanced) {
+        super.addInformation(stack, world, tooltip, advanced);
+        tooltip.add(I18n.format("gregtech.universal.disabled"));
+    }
+
     private class ObjectHolderHandler extends NotifiableItemStackHandler {
 
         public ObjectHolderHandler(MetaTileEntity metaTileEntity) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityOpticalDataHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityOpticalDataHatch.java
index ca4d8d8000c..054cbccfd19 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityOpticalDataHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityOpticalDataHatch.java
@@ -14,16 +14,20 @@
 import gregtech.client.renderer.texture.Textures;
 import gregtech.common.pipelike.optical.tile.TileEntityOpticalPipe;
 
+import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.ResourceLocation;
+import net.minecraft.world.World;
 import net.minecraftforge.common.capabilities.Capability;
 
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 import java.util.List;
@@ -134,4 +138,11 @@ public MultiblockAbility getAbility() {
     public void registerAbilities(@NotNull List abilityList) {
         abilityList.add(this);
     }
+
+    @Override
+    public void addInformation(ItemStack stack, @Nullable World world, @NotNull List tooltip,
+                               boolean advanced) {
+        super.addInformation(stack, world, tooltip, advanced);
+        tooltip.add(I18n.format("gregtech.universal.disabled"));
+    }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityReservoirHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityReservoirHatch.java
index 9981538731d..e8cf6d7b59b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityReservoirHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityReservoirHatch.java
@@ -63,7 +63,6 @@ public void update() {
         super.update();
         if (!getWorld().isRemote) {
             fillContainerFromInternalTank(fluidTank);
-            fillInternalTankFromFluidContainer(fluidTank);
             if (getOffsetTimer() % 20 == 0) {
                 fluidTank.refillWater();
             }
@@ -129,7 +128,7 @@ public ModularUI.Builder createTankUI(IFluidTank fluidTank, String title, Entity
 
         // Add input/output-specific widgets
         tankWidget = new TankWidget(fluidTank, 69, 52, 18, 18)
-                .setAlwaysShowFull(true).setDrawHoveringText(false).setContainerClicking(true, true);
+                .setAlwaysShowFull(true).setDrawHoveringText(false).setContainerClicking(true, false);
 
         builder.image(7, 16, 81, 55, GuiTextures.DISPLAY)
                 .widget(new ImageWidget(91, 36, 14, 15, GuiTextures.TANK_ICON))
@@ -180,47 +179,39 @@ public void addToolUsages(ItemStack stack, @Nullable World world, List t
 
     private static class InfiniteWaterTank extends NotifiableFluidTank {
 
-        private final FluidStack BIG_WATER = new FluidStack(FluidRegistry.WATER, FLUID_AMOUNT);
-
         public InfiniteWaterTank(int capacity, MetaTileEntity entityToNotify) {
             super(capacity, entityToNotify, false);
-            setFluid(BIG_WATER);
+            // start with the full amount
+            setFluid(new FluidStack(FluidRegistry.WATER, FLUID_AMOUNT));
+            // don't allow external callers to fill this tank
+            setCanFill(false);
         }
 
         private void refillWater() {
-            if (BIG_WATER.amount != FLUID_AMOUNT) {
-                BIG_WATER.amount = FLUID_AMOUNT;
-                onContentsChanged();
+            int fillAmount = Math.max(0, FLUID_AMOUNT - getFluidAmount());
+            if (fillAmount > 0) {
+                // call super since our overrides don't allow any kind of filling
+                super.fillInternal(new FluidStack(FluidRegistry.WATER, fillAmount), true);
             }
         }
 
         @Override
-        public FluidStack drainInternal(int maxDrain, boolean doDrain) {
-            return new FluidStack(BIG_WATER, maxDrain);
-        }
-
-        @Nullable
-        @Override
-        public FluidStack drainInternal(FluidStack resource, boolean doDrain) {
-            return new FluidStack(BIG_WATER, resource.amount);
+        public boolean canDrainFluidType(@Nullable FluidStack fluid) {
+            return fluid != null && fluid.getFluid() == FluidRegistry.WATER;
         }
 
+        // don't allow external filling
         @Override
         public int fillInternal(FluidStack resource, boolean doFill) {
-            return resource.amount;
-        }
-
-        @Override
-        public boolean canDrainFluidType(@Nullable FluidStack fluid) {
-            return fluid != null && fluid.getFluid() == BIG_WATER.getFluid();
+            return 0;
         }
 
         @Override
         public boolean canFillFluidType(FluidStack fluid) {
-            return fluid.getFluid() == BIG_WATER.getFluid();
+            return false;
         }
 
-        // serialization is unnecessary here, it should always have the same amount of fluid
+        // serialization is unnecessary here, we can always recreate it completely full since it would refill anyway
         @Override
         public FluidTank readFromNBT(NBTTagCompound nbt) {
             return this;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityRotorHolder.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityRotorHolder.java
index e36b9bb24d4..d78b7fe5042 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityRotorHolder.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityRotorHolder.java
@@ -12,6 +12,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockAbilityPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
+import gregtech.api.util.RelativeDirection;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.common.items.behaviors.AbstractMaterialPartBehavior;
 import gregtech.common.items.behaviors.TurbineRotorBehavior;
@@ -163,14 +164,17 @@ public boolean isFrontFaceFree() {
     }
 
     private boolean checkTurbineFaceFree() {
-        EnumFacing facing = getFrontFacing();
-        boolean permuteXZ = facing.getAxis() == EnumFacing.Axis.Z;
-        BlockPos centerPos = getPos().offset(facing);
-        for (int x = -1; x < 2; x++) {
-            for (int y = -1; y < 2; y++) {
-                BlockPos blockPos = centerPos.add(permuteXZ ? x : 0, y, permuteXZ ? 0 : x);
-                IBlockState blockState = getWorld().getBlockState(blockPos);
-                if (!blockState.getBlock().isAir(blockState, getWorld(), blockPos)) {
+        final EnumFacing front = getFrontFacing();
+        // this can be anything really, as long as it is not up/down when on Y axis
+        final EnumFacing upwards = front.getAxis() == EnumFacing.Axis.Y ? EnumFacing.NORTH : EnumFacing.UP;
+
+        for (int left = -1; left <= 1; left++) {
+            for (int up = -1; up <= 1; up++) {
+                // flip doesn't affect anything here since we are checking a square anyway
+                final BlockPos checkPos = RelativeDirection.offsetPos(
+                        getPos(), front, upwards, false, up, left, 1);
+                final IBlockState state = getWorld().getBlockState(checkPos);
+                if (!state.getBlock().isAir(state, getWorld(), checkPos)) {
                     return false;
                 }
             }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityAEHostablePart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityAEHostablePart.java
index 7f470e4d0ff..1c8f73e6837 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityAEHostablePart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityAEHostablePart.java
@@ -1,10 +1,6 @@
 package gregtech.common.metatileentities.multi.multiblockpart.appeng;
 
-import gregtech.api.GTValues;
 import gregtech.api.capability.IControllable;
-import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.client.renderer.ICubeRenderer;
-import gregtech.client.renderer.texture.Textures;
 import gregtech.common.ConfigHolder;
 import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityMultiblockNotifiablePart;
 
@@ -19,13 +15,12 @@
 import appeng.api.networking.GridFlags;
 import appeng.api.networking.security.IActionHost;
 import appeng.api.networking.security.IActionSource;
+import appeng.api.storage.IMEMonitor;
 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.IAEStack;
 import appeng.api.util.AECableType;
 import appeng.api.util.AEPartLocation;
+import appeng.me.GridAccessException;
 import appeng.me.helpers.AENetworkProxy;
 import appeng.me.helpers.BaseActionSource;
 import appeng.me.helpers.IGridProxyable;
@@ -36,28 +31,21 @@
 import java.io.IOException;
 import java.util.EnumSet;
 
-/**
- * @Author GlodBlock
- * @Description It can connect to ME network.
- * @Date 2023/4/18-23:17
- */
-public abstract class MetaTileEntityAEHostablePart extends MetaTileEntityMultiblockNotifiablePart
-                                                   implements IControllable {
+import static gregtech.api.capability.GregtechDataCodes.UPDATE_ONLINE_STATUS;
 
-    protected static final IStorageChannel ITEM_NET = AEApi.instance().storage()
-            .getStorageChannel(IItemStorageChannel.class);
-    protected static final IStorageChannel FLUID_NET = AEApi.instance().storage()
-            .getStorageChannel(IFluidStorageChannel.class);
+public abstract class MetaTileEntityAEHostablePart> extends MetaTileEntityMultiblockNotifiablePart
+                                                  implements IControllable {
 
-    private final static int ME_UPDATE_INTERVAL = ConfigHolder.compat.ae2.updateIntervals;
+    private final Class> storageChannel;
     private AENetworkProxy aeProxy;
     private int meUpdateTick;
     protected boolean isOnline;
-    private final static int ONLINE_ID = 6666;
 
-    public MetaTileEntityAEHostablePart(ResourceLocation metaTileEntityId, int tier, boolean isExportHatch) {
+    public MetaTileEntityAEHostablePart(ResourceLocation metaTileEntityId, int tier, boolean isExportHatch,
+                                        Class> storageChannel) {
         super(metaTileEntityId, tier, isExportHatch);
         this.meUpdateTick = 0;
+        this.storageChannel = storageChannel;
     }
 
     @Override
@@ -73,9 +61,7 @@ public void update() {
      * So there is no need to drop them.
      */
     @Override
-    public void clearMachineInventory(NonNullList itemBuffer) {
-        // NO-OP
-    }
+    public void clearMachineInventory(NonNullList itemBuffer) {}
 
     @Override
     public void writeInitialSyncData(PacketBuffer buf) {
@@ -114,24 +100,9 @@ public void receiveInitialSyncData(PacketBuffer buf) {
     @Override
     public void receiveCustomData(int dataId, PacketBuffer buf) {
         super.receiveCustomData(dataId, buf);
-        if (dataId == ONLINE_ID) {
+        if (dataId == UPDATE_ONLINE_STATUS) {
             this.isOnline = buf.readBoolean();
-        }
-    }
-
-    @Override
-    public ICubeRenderer getBaseTexture() {
-        MultiblockControllerBase controller = getController();
-        if (controller != null) {
-            return this.hatchTexture = controller.getBaseTexture(this);
-        } else if (this.hatchTexture != null) {
-            if (hatchTexture != Textures.getInactiveTexture(hatchTexture)) {
-                return this.hatchTexture = Textures.getInactiveTexture(hatchTexture);
-            }
-            return this.hatchTexture;
-        } else {
-            // Always display as EV casing
-            return Textures.VOLTAGE_CASINGS[GTValues.EV];
+            scheduleRenderUpdate();
         }
     }
 
@@ -165,9 +136,7 @@ public void setFrontFacing(EnumFacing frontFacing) {
     }
 
     @Override
-    public void gridChanged() {
-        // NO-OP
-    }
+    public void gridChanged() {}
 
     /**
      * Update me network connection status.
@@ -180,26 +149,27 @@ public boolean updateMEStatus() {
         } else {
             this.isOnline = false;
         }
-        writeCustomData(ONLINE_ID, buf -> buf.writeBoolean(this.isOnline));
+        if (!getWorld().isRemote) {
+            writeCustomData(UPDATE_ONLINE_STATUS, buf -> buf.writeBoolean(this.isOnline));
+        }
         return this.isOnline;
     }
 
     protected boolean shouldSyncME() {
-        return this.meUpdateTick % ME_UPDATE_INTERVAL == 0;
+        return this.meUpdateTick % ConfigHolder.compat.ae2.updateIntervals == 0;
     }
 
     protected IActionSource getActionSource() {
-        if (this.getHolder() instanceof IActionHost) {
-            return new MachineSource((IActionHost) this.getHolder());
+        if (this.getHolder() instanceof IActionHost holder) {
+            return new MachineSource(holder);
         }
         return new BaseActionSource();
     }
 
     @Nullable
     private AENetworkProxy createProxy() {
-        if (this.getHolder() instanceof IGridProxyable) {
-            AENetworkProxy proxy = new AENetworkProxy((IGridProxyable) this.getHolder(), "mte_proxy",
-                    this.getStackForm(), true);
+        if (this.getHolder() instanceof IGridProxyable holder) {
+            AENetworkProxy proxy = new AENetworkProxy(holder, "mte_proxy", this.getStackForm(), true);
             proxy.setFlags(GridFlags.REQUIRE_CHANNEL);
             proxy.setIdlePowerUsage(ConfigHolder.compat.ae2.meHatchEnergyUsage);
             proxy.setValidSides(EnumSet.of(this.getFrontFacing()));
@@ -207,4 +177,23 @@ private AENetworkProxy createProxy() {
         }
         return null;
     }
+
+    @NotNull
+    protected IStorageChannel getStorageChannel() {
+        return AEApi.instance().storage().getStorageChannel(storageChannel);
+    }
+
+    @Nullable
+    protected IMEMonitor getMonitor() {
+        AENetworkProxy proxy = getProxy();
+        if (proxy == null) return null;
+
+        IStorageChannel channel = getStorageChannel();
+
+        try {
+            return proxy.getStorage().getInventory(channel);
+        } catch (GridAccessException ignored) {
+            return null;
+        }
+    }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java
index 18a81b7df3d..3d4fea9abc0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java
@@ -3,15 +3,26 @@
 import gregtech.api.GTValues;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.GregtechTileCapabilities;
+import gregtech.api.capability.IDataStickIntractable;
+import gregtech.api.capability.IGhostSlotConfigurable;
+import gregtech.api.capability.INotifiableHandler;
+import gregtech.api.capability.impl.GhostCircuitItemStackHandler;
+import gregtech.api.capability.impl.ItemHandlerList;
 import gregtech.api.capability.impl.NotifiableItemStackHandler;
 import gregtech.api.gui.GuiTextures;
 import gregtech.api.gui.ModularUI;
+import gregtech.api.gui.widgets.GhostCircuitSlotWidget;
+import gregtech.api.gui.widgets.SlotWidget;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockAbilityPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.util.GTUtility;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.common.gui.widget.appeng.AEItemConfigWidget;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEItemList;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEItemSlot;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedItemStack;
 
 import net.minecraft.client.resources.I18n;
@@ -23,117 +34,160 @@
 import net.minecraft.network.PacketBuffer;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.TextComponentTranslation;
 import net.minecraft.world.World;
 import net.minecraftforge.common.capabilities.Capability;
+import net.minecraftforge.items.IItemHandler;
 import net.minecraftforge.items.IItemHandlerModifiable;
 
 import appeng.api.config.Actionable;
 import appeng.api.storage.IMEMonitor;
+import appeng.api.storage.channels.IItemStorageChannel;
 import appeng.api.storage.data.IAEItemStack;
-import appeng.me.GridAccessException;
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.Arrays;
 import java.util.List;
-import java.util.function.Consumer;
 
-/**
- * @Author GlodBlock
- * @Description The Input Bus that can auto fetch item ME storage network.
- * @Date 2023/4/22-13:34
- */
-public class MetaTileEntityMEInputBus extends MetaTileEntityAEHostablePart
-                                      implements IMultiblockAbilityPart {
+public class MetaTileEntityMEInputBus extends MetaTileEntityAEHostablePart
+                                      implements IMultiblockAbilityPart,
+                                      IGhostSlotConfigurable, IDataStickIntractable {
 
     public final static String ITEM_BUFFER_TAG = "ItemSlots";
     public final static String WORKING_TAG = "WorkingEnabled";
     private final static int CONFIG_SIZE = 16;
-    private boolean workingEnabled;
-    private ExportOnlyAEItemList aeItemHandler;
+    private boolean workingEnabled = true;
+    protected ExportOnlyAEItemList aeItemHandler;
+    protected GhostCircuitItemStackHandler circuitInventory;
+    protected NotifiableItemStackHandler extraSlotInventory;
+    private ItemHandlerList actualImportItems;
 
     public MetaTileEntityMEInputBus(ResourceLocation metaTileEntityId) {
-        super(metaTileEntityId, GTValues.UHV, false);
-        this.workingEnabled = true;
+        this(metaTileEntityId, GTValues.EV);
+    }
+
+    protected MetaTileEntityMEInputBus(ResourceLocation metaTileEntityId, int tier) {
+        super(metaTileEntityId, tier, false, IItemStorageChannel.class);
+    }
+
+    protected ExportOnlyAEItemList getAEItemHandler() {
+        if (aeItemHandler == null) {
+            aeItemHandler = new ExportOnlyAEItemList(this, CONFIG_SIZE, this.getController());
+        }
+        return aeItemHandler;
     }
 
     @Override
     protected void initializeInventory() {
-        this.aeItemHandler = new ExportOnlyAEItemList(this, CONFIG_SIZE, this.getController());
         super.initializeInventory();
-    }
-
-    protected IItemHandlerModifiable createImportItemHandler() {
-        return this.aeItemHandler;
+        this.aeItemHandler = getAEItemHandler();
+        this.circuitInventory = new GhostCircuitItemStackHandler(this);
+        this.circuitInventory.addNotifiableMetaTileEntity(this);
+        this.extraSlotInventory = new NotifiableItemStackHandler(this, 1, this, false);
+        this.extraSlotInventory.addNotifiableMetaTileEntity(this);
+        this.actualImportItems = new ItemHandlerList(
+                Arrays.asList(this.aeItemHandler, this.circuitInventory, this.extraSlotInventory));
+        this.importItems = this.actualImportItems;
     }
 
     public IItemHandlerModifiable getImportItems() {
-        this.importItems = this.aeItemHandler;
-        return super.getImportItems();
+        return this.actualImportItems;
     }
 
     @Override
     public void update() {
         super.update();
-        if (!getWorld().isRemote && this.workingEnabled && this.shouldSyncME()) {
-            if (this.updateMEStatus()) {
-                try {
-                    IMEMonitor aeNetwork = this.getProxy().getStorage().getInventory(ITEM_NET);
-                    for (ExportOnlyAEItem aeSlot : this.aeItemHandler.inventory) {
-                        // Try to clear the wrong item
-                        IAEItemStack exceedItem = aeSlot.exceedStack();
-                        if (exceedItem != null) {
-                            long total = exceedItem.getStackSize();
-                            IAEItemStack notInserted = aeNetwork.injectItems(exceedItem, Actionable.MODULATE,
-                                    this.getActionSource());
-                            if (notInserted != null && notInserted.getStackSize() > 0) {
-                                aeSlot.extractItem(0, (int) (total - notInserted.getStackSize()), false);
-                                continue;
-                            } else {
-                                aeSlot.extractItem(0, (int) total, false);
-                            }
-                        }
-                        // Fill it
-                        IAEItemStack reqItem = aeSlot.requestStack();
-                        if (reqItem != null) {
-                            IAEItemStack extracted = aeNetwork.extractItems(reqItem, Actionable.MODULATE,
-                                    this.getActionSource());
-                            if (extracted != null) {
-                                aeSlot.addStack(extracted);
-                            }
-                        }
-                    }
-                } catch (GridAccessException ignore) {}
-            }
+        if (!getWorld().isRemote && this.workingEnabled && updateMEStatus() && shouldSyncME()) {
+            syncME();
         }
     }
 
-    @Override
-    public void onRemoval() {
-        try {
-            IMEMonitor aeNetwork = this.getProxy().getStorage().getInventory(ITEM_NET);
-            for (ExportOnlyAEItem aeSlot : this.aeItemHandler.inventory) {
-                IAEItemStack stock = aeSlot.stock;
-                if (stock instanceof WrappedItemStack) {
-                    stock = ((WrappedItemStack) stock).getAEStack();
+    protected void syncME() {
+        IMEMonitor monitor = getMonitor();
+        if (monitor == null) return;
+
+        for (ExportOnlyAEItemSlot aeSlot : this.getAEItemHandler().getInventory()) {
+            // Try to clear the wrong item
+            IAEItemStack exceedItem = aeSlot.exceedStack();
+            if (exceedItem != null) {
+                long total = exceedItem.getStackSize();
+                IAEItemStack notInserted = monitor.injectItems(exceedItem, Actionable.MODULATE, this.getActionSource());
+                if (notInserted != null && notInserted.getStackSize() > 0) {
+                    aeSlot.extractItem(0, (int) (total - notInserted.getStackSize()), false);
+                    continue;
+                } else {
+                    aeSlot.extractItem(0, (int) total, false);
                 }
-                if (stock != null) {
-                    aeNetwork.injectItems(stock, Actionable.MODULATE, this.getActionSource());
+            }
+            // Fill it
+            IAEItemStack reqItem = aeSlot.requestStack();
+            if (reqItem != null) {
+                IAEItemStack extracted = monitor.extractItems(reqItem, Actionable.MODULATE, this.getActionSource());
+                if (extracted != null) {
+                    aeSlot.addStack(extracted);
                 }
             }
-        } catch (GridAccessException ignore) {}
+        }
+    }
+
+    @Override
+    public void onRemoval() {
+        flushInventory();
         super.onRemoval();
     }
 
+    protected void flushInventory() {
+        IMEMonitor monitor = getMonitor();
+        if (monitor == null) return;
+
+        for (ExportOnlyAEItemSlot aeSlot : this.getAEItemHandler().getInventory()) {
+            IAEItemStack stock = aeSlot.getStock();
+            if (stock instanceof WrappedItemStack) {
+                stock = ((WrappedItemStack) stock).getAEStack();
+            }
+            if (stock != null) {
+                monitor.injectItems(stock, Actionable.MODULATE, this.getActionSource());
+            }
+        }
+    }
+
     @Override
     public MetaTileEntity createMetaTileEntity(IGregTechTileEntity iGregTechTileEntity) {
-        return new MetaTileEntityMEInputBus(this.metaTileEntityId);
+        return new MetaTileEntityMEInputBus(metaTileEntityId);
+    }
+
+    @Override
+    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+        super.addToMultiBlock(controllerBase);
+        for (IItemHandler handler : this.actualImportItems.getBackingHandlers()) {
+            if (handler instanceof INotifiableHandler notifiable) {
+                notifiable.addNotifiableMetaTileEntity(controllerBase);
+                notifiable.addToNotifiedList(this, handler, false);
+            }
+        }
     }
 
     @Override
-    protected ModularUI createUI(EntityPlayer entityPlayer) {
+    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+        super.removeFromMultiBlock(controllerBase);
+        for (IItemHandler handler : this.actualImportItems.getBackingHandlers()) {
+            if (handler instanceof INotifiableHandler notifiable) {
+                notifiable.removeNotifiableMetaTileEntity(controllerBase);
+            }
+        }
+    }
+
+    @Override
+    protected final ModularUI createUI(EntityPlayer player) {
+        ModularUI.Builder builder = createUITemplate(player);
+        return builder.build(this.getHolder(), player);
+    }
+
+    protected ModularUI.Builder createUITemplate(EntityPlayer player) {
         ModularUI.Builder builder = ModularUI
                 .builder(GuiTextures.BACKGROUND, 176, 18 + 18 * 4 + 94)
                 .label(10, 5, getMetaFullName());
@@ -141,13 +195,37 @@ protected ModularUI createUI(EntityPlayer entityPlayer) {
         builder.dynamicLabel(10, 15, () -> this.isOnline ?
                 I18n.format("gregtech.gui.me_network.online") :
                 I18n.format("gregtech.gui.me_network.offline"),
-                0xFFFFFFFF);
+                0x404040);
 
         // Config slots
-        builder.widget(new AEItemConfigWidget(16, 25, this.aeItemHandler.inventory));
+        builder.widget(new AEItemConfigWidget(7, 25, this.getAEItemHandler()));
+
+        // Ghost circuit slot
+        SlotWidget circuitSlot = new GhostCircuitSlotWidget(circuitInventory, 0, 7 + 18 * 4, 25 + 18 * 3)
+                .setBackgroundTexture(GuiTextures.SLOT, GuiTextures.INT_CIRCUIT_OVERLAY);
+        builder.widget(circuitSlot.setConsumer(w -> {
+            String configString;
+            if (circuitInventory == null ||
+                    circuitInventory.getCircuitValue() == GhostCircuitItemStackHandler.NO_CONFIG) {
+                configString = new TextComponentTranslation("gregtech.gui.configurator_slot.no_value")
+                        .getFormattedText();
+            } else {
+                configString = String.valueOf(circuitInventory.getCircuitValue());
+            }
+
+            w.setTooltipText("gregtech.gui.configurator_slot.tooltip", configString);
+        }));
 
-        builder.bindPlayerInventory(entityPlayer.inventory, GuiTextures.SLOT, 7, 18 + 18 * 4 + 12);
-        return builder.build(this.getHolder(), entityPlayer);
+        // Extra slot
+        builder.widget(new SlotWidget(extraSlotInventory, 0, 7 + 18 * 4, 25 + 18 * 2)
+                .setBackgroundTexture(GuiTextures.SLOT)
+                .setTooltipText("gregtech.gui.me_bus.extra_slot"));
+
+        // Arrow image
+        builder.image(7 + 18 * 4, 25 + 18, 18, 18, GuiTextures.ARROW_DOUBLE);
+
+        builder.bindPlayerInventory(player.inventory, GuiTextures.SLOT, 7, 18 + 18 * 4 + 12);
+        return builder;
     }
 
     @Override
@@ -190,13 +268,16 @@ public NBTTagCompound writeToNBT(NBTTagCompound data) {
         data.setBoolean(WORKING_TAG, this.workingEnabled);
         NBTTagList slots = new NBTTagList();
         for (int i = 0; i < CONFIG_SIZE; i++) {
-            ExportOnlyAEItem slot = this.aeItemHandler.inventory[i];
+            ExportOnlyAEItemSlot slot = this.getAEItemHandler().getInventory()[i];
             NBTTagCompound slotTag = new NBTTagCompound();
             slotTag.setInteger("slot", i);
             slotTag.setTag("stack", slot.serializeNBT());
             slots.appendTag(slotTag);
         }
         data.setTag(ITEM_BUFFER_TAG, slots);
+        this.circuitInventory.write(data);
+        // Extra slot inventory
+        GTUtility.writeItems(this.extraSlotInventory, "ExtraInventory", data);
         return data;
     }
 
@@ -210,10 +291,12 @@ public void readFromNBT(NBTTagCompound data) {
             NBTTagList slots = (NBTTagList) data.getTag(ITEM_BUFFER_TAG);
             for (NBTBase nbtBase : slots) {
                 NBTTagCompound slotTag = (NBTTagCompound) nbtBase;
-                ExportOnlyAEItem slot = this.aeItemHandler.inventory[slotTag.getInteger("slot")];
+                ExportOnlyAEItemSlot slot = this.getAEItemHandler().getInventory()[slotTag.getInteger("slot")];
                 slot.deserializeNBT(slotTag.getCompoundTag("stack"));
             }
         }
+        this.circuitInventory.read(data);
+        GTUtility.readItems(this.extraSlotInventory, "ExtraInventory", data);
         this.importItems = createImportItemHandler();
     }
 
@@ -221,7 +304,11 @@ public void readFromNBT(NBTTagCompound data) {
     public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline) {
         super.renderMetaTileEntity(renderState, translation, pipeline);
         if (this.shouldRenderOverlay()) {
-            Textures.ME_INPUT_BUS.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            if (isOnline) {
+                Textures.ME_INPUT_BUS_ACTIVE.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            } else {
+                Textures.ME_INPUT_BUS.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            }
         }
     }
 
@@ -231,6 +318,8 @@ public void addInformation(ItemStack stack, @Nullable World player, @NotNull Lis
         super.addInformation(stack, player, tooltip, advanced);
         tooltip.add(I18n.format("gregtech.machine.item_bus.import.tooltip"));
         tooltip.add(I18n.format("gregtech.machine.me.item_import.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me_import_item_hatch.configs.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.copy_paste.tooltip"));
         tooltip.add(I18n.format("gregtech.universal.enabled"));
     }
 
@@ -241,197 +330,79 @@ public MultiblockAbility getAbility() {
 
     @Override
     public void registerAbilities(List list) {
-        list.add(this.aeItemHandler);
+        list.add(this.actualImportItems);
     }
 
-    private static class ExportOnlyAEItemList extends NotifiableItemStackHandler {
-
-        ExportOnlyAEItem[] inventory;
-
-        public ExportOnlyAEItemList(MetaTileEntity holder, int slots, MetaTileEntity entityToNotify) {
-            super(holder, slots, entityToNotify, false);
-            this.inventory = new ExportOnlyAEItem[CONFIG_SIZE];
-            for (int i = 0; i < CONFIG_SIZE; i++) {
-                this.inventory[i] = new ExportOnlyAEItem(null, null);
-            }
-            for (ExportOnlyAEItem slot : this.inventory) {
-                slot.trigger = this::onContentsChanged;
-            }
-        }
-
-        @Override
-        public void deserializeNBT(NBTTagCompound nbt) {
-            for (int index = 0; index < CONFIG_SIZE; index++) {
-                if (nbt.hasKey("#" + index)) {
-                    NBTTagCompound slotTag = nbt.getCompoundTag("#" + index);
-                    this.inventory[index].deserializeNBT(slotTag);
-                }
-            }
-        }
-
-        @Override
-        public NBTTagCompound serializeNBT() {
-            NBTTagCompound nbt = new NBTTagCompound();
-            for (int index = 0; index < CONFIG_SIZE; index++) {
-                NBTTagCompound slot = this.inventory[index].serializeNBT();
-                nbt.setTag("#" + index, slot);
-            }
-            return nbt;
-        }
-
-        @Override
-        public void setStackInSlot(int slot, @NotNull ItemStack stack) {
-            // NO-OP
-        }
-
-        @Override
-        public int getSlots() {
-            return MetaTileEntityMEInputBus.CONFIG_SIZE;
-        }
-
-        @NotNull
-        @Override
-        public ItemStack getStackInSlot(int slot) {
-            if (slot >= 0 && slot < CONFIG_SIZE) {
-                return this.inventory[slot].getStackInSlot(0);
-            }
-            return ItemStack.EMPTY;
-        }
-
-        @NotNull
-        @Override
-        public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) {
-            return stack;
-        }
-
-        @NotNull
-        @Override
-        public ItemStack extractItem(int slot, int amount, boolean simulate) {
-            if (slot >= 0 && slot < CONFIG_SIZE) {
-                return this.inventory[slot].extractItem(0, amount, simulate);
-            }
-            return ItemStack.EMPTY;
-        }
-
-        @Override
-        public int getSlotLimit(int slot) {
-            return Integer.MAX_VALUE;
-        }
-
-        @Override
-        protected int getStackLimit(int slot, @NotNull ItemStack stack) {
-            return Integer.MAX_VALUE;
-        }
+    @Override
+    public boolean hasGhostCircuitInventory() {
+        return true;
     }
 
-    public static class ExportOnlyAEItem extends ExportOnlyAESlot implements IItemHandlerModifiable {
-
-        private Consumer trigger;
-
-        public ExportOnlyAEItem(IAEItemStack config, IAEItemStack stock) {
-            super(config, stock);
-        }
-
-        public ExportOnlyAEItem() {
-            super();
-        }
-
-        @Override
-        public void deserializeNBT(NBTTagCompound nbt) {
-            if (nbt.hasKey(CONFIG_TAG)) {
-                this.config = WrappedItemStack.fromNBT(nbt.getCompoundTag(CONFIG_TAG));
-            }
-            if (nbt.hasKey(STOCK_TAG)) {
-                this.stock = WrappedItemStack.fromNBT(nbt.getCompoundTag(STOCK_TAG));
-            }
-        }
-
-        @Override
-        public ExportOnlyAEItem copy() {
-            return new ExportOnlyAEItem(
-                    this.config == null ? null : this.config.copy(),
-                    this.stock == null ? null : this.stock.copy());
+    @Override
+    public void setGhostCircuitConfig(int config) {
+        if (this.circuitInventory.getCircuitValue() == config) {
+            return;
         }
-
-        @Override
-        public void setStackInSlot(int slot, @NotNull ItemStack stack) {
-            // NO-OP
+        this.circuitInventory.setCircuitValue(config);
+        if (!getWorld().isRemote) {
+            markDirty();
         }
+    }
 
-        @Override
-        public int getSlots() {
-            return 1;
-        }
+    @Override
+    public final void onDataStickLeftClick(EntityPlayer player, ItemStack dataStick) {
+        NBTTagCompound tag = new NBTTagCompound();
+        tag.setTag("MEInputBus", writeConfigToTag());
+        dataStick.setTagCompound(tag);
+        dataStick.setTranslatableName("gregtech.machine.me.item_import.data_stick.name");
+        player.sendStatusMessage(new TextComponentTranslation("gregtech.machine.me.import_copy_settings"), true);
+    }
 
-        @NotNull
-        @Override
-        public ItemStack getStackInSlot(int slot) {
-            if (slot == 0 && this.stock != null) {
-                return this.stock.getDefinition();
+    protected NBTTagCompound writeConfigToTag() {
+        NBTTagCompound tag = new NBTTagCompound();
+        NBTTagCompound configStacks = new NBTTagCompound();
+        tag.setTag("ConfigStacks", configStacks);
+        for (int i = 0; i < CONFIG_SIZE; i++) {
+            var slot = this.aeItemHandler.getInventory()[i];
+            IAEItemStack config = slot.getConfig();
+            if (config == null) {
+                continue;
             }
-            return ItemStack.EMPTY;
+            NBTTagCompound stackNbt = new NBTTagCompound();
+            config.getDefinition().writeToNBT(stackNbt);
+            configStacks.setTag(Integer.toString(i), stackNbt);
         }
+        tag.setByte("GhostCircuit", (byte) this.circuitInventory.getCircuitValue());
+        return tag;
+    }
 
-        @NotNull
-        @Override
-        public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) {
-            return stack;
+    @Override
+    public final boolean onDataStickRightClick(EntityPlayer player, ItemStack dataStick) {
+        NBTTagCompound tag = dataStick.getTagCompound();
+        if (tag == null || !tag.hasKey("MEInputBus")) {
+            return false;
         }
+        readConfigFromTag(tag.getCompoundTag("MEInputBus"));
+        syncME();
+        player.sendStatusMessage(new TextComponentTranslation("gregtech.machine.me.import_paste_settings"), true);
+        return true;
+    }
 
-        @NotNull
-        @Override
-        public ItemStack extractItem(int slot, int amount, boolean simulate) {
-            if (slot == 0 && this.stock != null) {
-                int extracted = (int) Math.min(this.stock.getStackSize(), amount);
-                ItemStack result = this.stock.createItemStack();
-                result.setCount(extracted);
-                if (!simulate) {
-                    this.stock.decStackSize(extracted);
-                    if (this.stock.getStackSize() == 0) {
-                        this.stock = null;
-                    }
-                }
-                if (this.trigger != null) {
-                    this.trigger.accept(0);
+    protected void readConfigFromTag(NBTTagCompound tag) {
+        if (tag.hasKey("ConfigStacks")) {
+            NBTTagCompound configStacks = tag.getCompoundTag("ConfigStacks");
+            for (int i = 0; i < CONFIG_SIZE; i++) {
+                String key = Integer.toString(i);
+                if (configStacks.hasKey(key)) {
+                    NBTTagCompound configTag = configStacks.getCompoundTag(key);
+                    this.aeItemHandler.getInventory()[i].setConfig(WrappedItemStack.fromNBT(configTag));
+                } else {
+                    this.aeItemHandler.getInventory()[i].setConfig(null);
                 }
-                return result;
             }
-            return ItemStack.EMPTY;
         }
-
-        @Override
-        public IAEItemStack requestStack() {
-            IAEItemStack result = super.requestStack();
-            if (result instanceof WrappedItemStack) {
-                return ((WrappedItemStack) result).getAEStack();
-            } else {
-                return result;
-            }
-        }
-
-        @Override
-        public IAEItemStack exceedStack() {
-            IAEItemStack result = super.exceedStack();
-            if (result instanceof WrappedItemStack) {
-                return ((WrappedItemStack) result).getAEStack();
-            } else {
-                return result;
-            }
-        }
-
-        @Override
-        public void addStack(IAEItemStack stack) {
-            if (this.stock == null) {
-                this.stock = WrappedItemStack.fromItemStack(stack.createItemStack());
-            } else {
-                this.stock.add(stack);
-            }
-            this.trigger.accept(0);
-        }
-
-        @Override
-        public int getSlotLimit(int slot) {
-            return Integer.MAX_VALUE;
+        if (tag.hasKey("GhostCircuit")) {
+            this.setGhostCircuitConfig(tag.getByte("GhostCircuit"));
         }
     }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputHatch.java
index 9a6dfa4f6aa..4e510077787 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputHatch.java
@@ -3,16 +3,19 @@
 import gregtech.api.GTValues;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.GregtechTileCapabilities;
-import gregtech.api.capability.INotifiableHandler;
+import gregtech.api.capability.IDataStickIntractable;
 import gregtech.api.capability.impl.FluidTankList;
 import gregtech.api.gui.GuiTextures;
 import gregtech.api.gui.ModularUI;
+import gregtech.api.gui.widgets.ImageWidget;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockAbilityPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.common.gui.widget.appeng.AEFluidConfigWidget;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEFluidList;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEFluidSlot;
 import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedFluidStack;
 
 import net.minecraft.client.resources.I18n;
@@ -24,122 +27,129 @@
 import net.minecraft.network.PacketBuffer;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.TextComponentTranslation;
 import net.minecraft.world.World;
 import net.minecraftforge.common.capabilities.Capability;
-import net.minecraftforge.fluids.FluidStack;
-import net.minecraftforge.fluids.FluidTankInfo;
 import net.minecraftforge.fluids.IFluidTank;
-import net.minecraftforge.fluids.capability.FluidTankProperties;
-import net.minecraftforge.fluids.capability.IFluidHandler;
-import net.minecraftforge.fluids.capability.IFluidTankProperties;
 
 import appeng.api.config.Actionable;
 import appeng.api.storage.IMEMonitor;
+import appeng.api.storage.channels.IFluidStorageChannel;
 import appeng.api.storage.data.IAEFluidStack;
-import appeng.me.GridAccessException;
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-/**
- * @Author GlodBlock
- * @Description The Input Hatch that can auto fetch fluid ME storage network.
- * @Date 2023/4/20-21:21
- */
-public class MetaTileEntityMEInputHatch extends MetaTileEntityAEHostablePart
-                                        implements IMultiblockAbilityPart {
+public class MetaTileEntityMEInputHatch extends MetaTileEntityAEHostablePart
+                                        implements IMultiblockAbilityPart, IDataStickIntractable {
 
     public final static String FLUID_BUFFER_TAG = "FluidTanks";
     public final static String WORKING_TAG = "WorkingEnabled";
     private final static int CONFIG_SIZE = 16;
-    private boolean workingEnabled;
-    private ExportOnlyAEFluid[] aeFluidTanks;
+    private boolean workingEnabled = true;
+    protected ExportOnlyAEFluidList aeFluidHandler;
 
     public MetaTileEntityMEInputHatch(ResourceLocation metaTileEntityId) {
-        super(metaTileEntityId, GTValues.UHV, false);
-        this.workingEnabled = true;
+        this(metaTileEntityId, GTValues.EV);
+    }
+
+    protected MetaTileEntityMEInputHatch(ResourceLocation metaTileEntityId, int tier) {
+        super(metaTileEntityId, tier, false, IFluidStorageChannel.class);
+    }
+
+    protected ExportOnlyAEFluidList getAEFluidHandler() {
+        if (aeFluidHandler == null) {
+            aeFluidHandler = new ExportOnlyAEFluidList(this, CONFIG_SIZE, this.getController());
+        }
+        return aeFluidHandler;
     }
 
     @Override
     protected void initializeInventory() {
-        this.aeFluidTanks = new ExportOnlyAEFluid[CONFIG_SIZE];
-        for (int i = 0; i < CONFIG_SIZE; i++) {
-            this.aeFluidTanks[i] = new ExportOnlyAEFluid(this, null, null, this.getController());
-        }
+        getAEFluidHandler(); // initialize it
         super.initializeInventory();
     }
 
     @Override
     protected FluidTankList createImportFluidHandler() {
-        return new FluidTankList(false, this.aeFluidTanks);
+        return new FluidTankList(false, getAEFluidHandler().getInventory());
     }
 
     @Override
     public void update() {
         super.update();
-        if (!getWorld().isRemote && this.workingEnabled && this.shouldSyncME()) {
-            if (this.updateMEStatus()) {
-                try {
-                    IMEMonitor aeNetwork = this.getProxy().getStorage().getInventory(FLUID_NET);
-                    for (ExportOnlyAEFluid aeTank : this.aeFluidTanks) {
-                        // Try to clear the wrong fluid
-                        IAEFluidStack exceedFluid = aeTank.exceedStack();
-                        if (exceedFluid != null) {
-                            long total = exceedFluid.getStackSize();
-                            IAEFluidStack notInserted = aeNetwork.injectItems(exceedFluid, Actionable.MODULATE,
-                                    this.getActionSource());
-                            if (notInserted != null && notInserted.getStackSize() > 0) {
-                                aeTank.drain((int) (total - notInserted.getStackSize()), true);
-                                continue;
-                            } else {
-                                aeTank.drain((int) total, true);
-                            }
-                        }
-                        // Fill it
-                        IAEFluidStack reqFluid = aeTank.requestStack();
-                        if (reqFluid != null) {
-                            IAEFluidStack extracted = aeNetwork.extractItems(reqFluid, Actionable.MODULATE,
-                                    this.getActionSource());
-                            if (extracted != null) {
-                                aeTank.addStack(extracted);
-                            }
-                        }
-                    }
-                } catch (GridAccessException ignore) {}
-            }
+        if (!getWorld().isRemote && this.workingEnabled && this.shouldSyncME() && updateMEStatus()) {
+            syncME();
         }
     }
 
-    @Override
-    public void onRemoval() {
-        try {
-            IMEMonitor aeNetwork = this.getProxy().getStorage().getInventory(FLUID_NET);
-            for (ExportOnlyAEFluid aeTank : this.aeFluidTanks) {
-                IAEFluidStack stock = aeTank.stock;
-                if (stock instanceof WrappedFluidStack) {
-                    stock = ((WrappedFluidStack) stock).getAEStack();
+    protected void syncME() {
+        IMEMonitor monitor = getMonitor();
+        if (monitor == null) return;
+
+        for (ExportOnlyAEFluidSlot aeTank : this.getAEFluidHandler().getInventory()) {
+            // Try to clear the wrong fluid
+            IAEFluidStack exceedFluid = aeTank.exceedStack();
+            if (exceedFluid != null) {
+                long total = exceedFluid.getStackSize();
+                IAEFluidStack notInserted = monitor.injectItems(exceedFluid, Actionable.MODULATE,
+                        this.getActionSource());
+                if (notInserted != null && notInserted.getStackSize() > 0) {
+                    aeTank.drain((int) (total - notInserted.getStackSize()), true);
+                    continue;
+                } else {
+                    aeTank.drain((int) total, true);
                 }
-                if (stock != null) {
-                    aeNetwork.injectItems(stock, Actionable.MODULATE, this.getActionSource());
+            }
+            // Fill it
+            IAEFluidStack reqFluid = aeTank.requestStack();
+            if (reqFluid != null) {
+                IAEFluidStack extracted = monitor.extractItems(reqFluid, Actionable.MODULATE, this.getActionSource());
+                if (extracted != null) {
+                    aeTank.addStack(extracted);
                 }
             }
-        } catch (GridAccessException ignore) {}
+        }
+    }
+
+    @Override
+    public void onRemoval() {
+        flushInventory();
         super.onRemoval();
     }
 
+    protected void flushInventory() {
+        IMEMonitor monitor = getMonitor();
+        if (monitor == null) return;
+
+        for (ExportOnlyAEFluidSlot aeTank : this.getAEFluidHandler().getInventory()) {
+            IAEFluidStack stock = aeTank.getStock();
+            if (stock instanceof WrappedFluidStack) {
+                stock = ((WrappedFluidStack) stock).getAEStack();
+            }
+            if (stock != null) {
+                monitor.injectItems(stock, Actionable.MODULATE, this.getActionSource());
+            }
+        }
+    }
+
     @Override
     public MetaTileEntity createMetaTileEntity(IGregTechTileEntity iGregTechTileEntity) {
         return new MetaTileEntityMEInputHatch(this.metaTileEntityId);
     }
 
     @Override
-    protected ModularUI createUI(EntityPlayer entityPlayer) {
+    protected final ModularUI createUI(EntityPlayer player) {
+        ModularUI.Builder builder = createUITemplate(player);
+        return builder.build(this.getHolder(), player);
+    }
+
+    protected ModularUI.Builder createUITemplate(EntityPlayer player) {
         ModularUI.Builder builder = ModularUI
                 .builder(GuiTextures.BACKGROUND, 176, 18 + 18 * 4 + 94)
                 .label(10, 5, getMetaFullName());
@@ -147,13 +157,21 @@ protected ModularUI createUI(EntityPlayer entityPlayer) {
         builder.dynamicLabel(10, 15, () -> this.isOnline ?
                 I18n.format("gregtech.gui.me_network.online") :
                 I18n.format("gregtech.gui.me_network.offline"),
-                0xFFFFFFFF);
+                0x404040);
 
         // Config slots
-        builder.widget(new AEFluidConfigWidget(16, 25, this.aeFluidTanks));
+        builder.widget(new AEFluidConfigWidget(7, 25, this.getAEFluidHandler()));
+
+        // Arrow image
+        builder.image(7 + 18 * 4, 25 + 18, 18, 18, GuiTextures.ARROW_DOUBLE);
 
-        builder.bindPlayerInventory(entityPlayer.inventory, GuiTextures.SLOT, 7, 18 + 18 * 4 + 12);
-        return builder.build(this.getHolder(), entityPlayer);
+        // GT Logo, cause there's some free real estate
+        builder.widget(new ImageWidget(7 + 18 * 4, 25 + 18 * 3, 17, 17,
+                GTValues.XMAS.get() ? GuiTextures.GREGTECH_LOGO_XMAS : GuiTextures.GREGTECH_LOGO)
+                        .setIgnoreColor(true));
+
+        builder.bindPlayerInventory(player.inventory, GuiTextures.SLOT, 7, 18 + 18 * 4 + 12);
+        return builder;
     }
 
     @Override
@@ -196,7 +214,7 @@ public NBTTagCompound writeToNBT(NBTTagCompound data) {
         data.setBoolean(WORKING_TAG, this.workingEnabled);
         NBTTagList tanks = new NBTTagList();
         for (int i = 0; i < CONFIG_SIZE; i++) {
-            ExportOnlyAEFluid tank = this.aeFluidTanks[i];
+            ExportOnlyAEFluidSlot tank = this.getAEFluidHandler().getInventory()[i];
             NBTTagCompound tankTag = new NBTTagCompound();
             tankTag.setInteger("slot", i);
             tankTag.setTag("tank", tank.serializeNBT());
@@ -216,7 +234,7 @@ public void readFromNBT(NBTTagCompound data) {
             NBTTagList tanks = (NBTTagList) data.getTag(FLUID_BUFFER_TAG);
             for (NBTBase nbtBase : tanks) {
                 NBTTagCompound tankTag = (NBTTagCompound) nbtBase;
-                ExportOnlyAEFluid tank = this.aeFluidTanks[tankTag.getInteger("slot")];
+                ExportOnlyAEFluidSlot tank = this.getAEFluidHandler().getInventory()[tankTag.getInteger("slot")];
                 tank.deserializeNBT(tankTag.getCompoundTag("tank"));
             }
         }
@@ -226,7 +244,11 @@ public void readFromNBT(NBTTagCompound data) {
     public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline) {
         super.renderMetaTileEntity(renderState, translation, pipeline);
         if (this.shouldRenderOverlay()) {
-            Textures.ME_INPUT_HATCH.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            if (isOnline) {
+                Textures.ME_INPUT_HATCH_ACTIVE.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            } else {
+                Textures.ME_INPUT_HATCH.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            }
         }
     }
 
@@ -236,6 +258,8 @@ public void addInformation(ItemStack stack, @Nullable World player, @NotNull Lis
         super.addInformation(stack, player, tooltip, advanced);
         tooltip.add(I18n.format("gregtech.machine.fluid_hatch.import.tooltip"));
         tooltip.add(I18n.format("gregtech.machine.me.fluid_import.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me_import_fluid_hatch.configs.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.copy_paste.tooltip"));
         tooltip.add(I18n.format("gregtech.universal.enabled"));
     }
 
@@ -246,157 +270,59 @@ public MultiblockAbility getAbility() {
 
     @Override
     public void registerAbilities(List list) {
-        list.addAll(Arrays.asList(this.aeFluidTanks));
+        list.addAll(Arrays.asList(this.getAEFluidHandler().getInventory()));
     }
 
-    public static class ExportOnlyAEFluid extends ExportOnlyAESlot
-                                          implements IFluidTank, INotifiableHandler, IFluidHandler {
-
-        private final List notifiableEntities = new ArrayList<>();
-        private MetaTileEntity holder;
-
-        public ExportOnlyAEFluid(MetaTileEntity holder, IAEFluidStack config, IAEFluidStack stock, MetaTileEntity mte) {
-            super(config, stock);
-            this.holder = holder;
-            this.notifiableEntities.add(mte);
-        }
-
-        public ExportOnlyAEFluid() {
-            super();
-        }
-
-        @Override
-        public IAEFluidStack requestStack() {
-            IAEFluidStack result = super.requestStack();
-            if (result instanceof WrappedFluidStack) {
-                return ((WrappedFluidStack) result).getAEStack();
-            } else {
-                return result;
-            }
-        }
-
-        @Override
-        public IAEFluidStack exceedStack() {
-            IAEFluidStack result = super.exceedStack();
-            if (result instanceof WrappedFluidStack) {
-                return ((WrappedFluidStack) result).getAEStack();
-            } else {
-                return result;
-            }
-        }
-
-        @Override
-        public void addStack(IAEFluidStack stack) {
-            if (this.stock == null) {
-                this.stock = WrappedFluidStack.fromFluidStack(stack.getFluidStack());
-            } else {
-                this.stock.add(stack);
-            }
-            trigger();
-        }
-
-        @Override
-        public void deserializeNBT(NBTTagCompound nbt) {
-            if (nbt.hasKey(CONFIG_TAG)) {
-                this.config = WrappedFluidStack.fromNBT(nbt.getCompoundTag(CONFIG_TAG));
-            }
-            if (nbt.hasKey(STOCK_TAG)) {
-                this.stock = WrappedFluidStack.fromNBT(nbt.getCompoundTag(STOCK_TAG));
-            }
-        }
-
-        @Nullable
-        @Override
-        public FluidStack getFluid() {
-            if (this.stock != null && this.stock instanceof WrappedFluidStack) {
-                return ((WrappedFluidStack) this.stock).getDelegate();
-            }
-            return null;
-        }
-
-        @Override
-        public int getFluidAmount() {
-            return this.stock != null ? (int) this.stock.getStackSize() : 0;
-        }
-
-        @Override
-        public int getCapacity() {
-            // Its capacity is always 0.
-            return 0;
-        }
-
-        @Override
-        public FluidTankInfo getInfo() {
-            return new FluidTankInfo(this);
-        }
-
-        @Override
-        public IFluidTankProperties[] getTankProperties() {
-            return new IFluidTankProperties[] {
-                    new FluidTankProperties(this.getFluid(), 0)
-            };
-        }
-
-        @Override
-        public int fill(FluidStack resource, boolean doFill) {
-            return 0;
-        }
-
-        @Nullable
-        @Override
-        public FluidStack drain(FluidStack resource, boolean doDrain) {
-            if (this.getFluid() != null && this.getFluid().isFluidEqual(resource)) {
-                return this.drain(resource.amount, doDrain);
-            }
-            return null;
-        }
+    @Override
+    public final void onDataStickLeftClick(EntityPlayer player, ItemStack dataStick) {
+        NBTTagCompound tag = new NBTTagCompound();
+        tag.setTag("MEInputHatch", writeConfigToTag());
+        dataStick.setTagCompound(tag);
+        dataStick.setTranslatableName("gregtech.machine.me.fluid_import.data_stick.name");
+        player.sendStatusMessage(new TextComponentTranslation("gregtech.machine.me.import_copy_settings"), true);
+    }
 
-        @Nullable
-        @Override
-        public FluidStack drain(int maxDrain, boolean doDrain) {
-            if (this.stock == null) {
-                return null;
-            }
-            int drained = (int) Math.min(this.stock.getStackSize(), maxDrain);
-            FluidStack result = new FluidStack(this.stock.getFluid(), drained);
-            if (doDrain) {
-                this.stock.decStackSize(drained);
-                if (this.stock.getStackSize() == 0) {
-                    this.stock = null;
-                }
-                trigger();
+    protected NBTTagCompound writeConfigToTag() {
+        NBTTagCompound tag = new NBTTagCompound();
+        NBTTagCompound configStacks = new NBTTagCompound();
+        tag.setTag("ConfigStacks", configStacks);
+        for (int i = 0; i < CONFIG_SIZE; i++) {
+            var slot = this.aeFluidHandler.getInventory()[i];
+            IAEFluidStack config = slot.getConfig();
+            if (config == null) {
+                continue;
             }
-            return result;
-        }
-
-        @Override
-        public void addNotifiableMetaTileEntity(MetaTileEntity metaTileEntity) {
-            this.notifiableEntities.add(metaTileEntity);
+            NBTTagCompound stackNbt = new NBTTagCompound();
+            config.writeToNBT(stackNbt);
+            configStacks.setTag(Integer.toString(i), stackNbt);
         }
+        return tag;
+    }
 
-        @Override
-        public void removeNotifiableMetaTileEntity(MetaTileEntity metaTileEntity) {
-            this.notifiableEntities.remove(metaTileEntity);
+    @Override
+    public final boolean onDataStickRightClick(EntityPlayer player, ItemStack dataStick) {
+        NBTTagCompound tag = dataStick.getTagCompound();
+        if (tag == null || !tag.hasKey("MEInputHatch")) {
+            return false;
         }
+        readConfigFromTag(tag.getCompoundTag("MEInputHatch"));
+        syncME();
+        player.sendStatusMessage(new TextComponentTranslation("gregtech.machine.me.import_paste_settings"), true);
+        return true;
+    }
 
-        private void trigger() {
-            for (MetaTileEntity metaTileEntity : this.notifiableEntities) {
-                if (metaTileEntity != null && metaTileEntity.isValid()) {
-                    this.addToNotifiedList(metaTileEntity, this, false);
+    protected void readConfigFromTag(NBTTagCompound tag) {
+        if (tag.hasKey("ConfigStacks")) {
+            NBTTagCompound configStacks = tag.getCompoundTag("ConfigStacks");
+            for (int i = 0; i < CONFIG_SIZE; i++) {
+                String key = Integer.toString(i);
+                if (configStacks.hasKey(key)) {
+                    NBTTagCompound configTag = configStacks.getCompoundTag(key);
+                    this.aeFluidHandler.getInventory()[i].setConfig(WrappedFluidStack.fromNBT(configTag));
+                } else {
+                    this.aeFluidHandler.getInventory()[i].setConfig(null);
                 }
             }
-            if (holder != null) {
-                holder.markDirty();
-            }
-        }
-
-        @Override
-        public ExportOnlyAEFluid copy() {
-            return new ExportOnlyAEFluid(
-                    this.holder,
-                    this.config == null ? null : this.config.copy(),
-                    this.stock == null ? null : this.stock.copy(),
-                    null);
         }
     }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java
index 92277985603..853321014f9 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java
@@ -30,9 +30,9 @@
 
 import appeng.api.config.Actionable;
 import appeng.api.storage.IMEMonitor;
+import appeng.api.storage.channels.IItemStorageChannel;
 import appeng.api.storage.data.IAEItemStack;
 import appeng.api.storage.data.IItemList;
-import appeng.me.GridAccessException;
 import appeng.util.item.AEItemStack;
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
@@ -43,22 +43,16 @@
 import java.util.ArrayList;
 import java.util.List;
 
-/**
- * @Author GlodBlock
- * @Description The Output Bus that can directly send its contents to ME storage network.
- * @Date 2023/4/19-20:37
- */
-public class MetaTileEntityMEOutputBus extends MetaTileEntityAEHostablePart
+public class MetaTileEntityMEOutputBus extends MetaTileEntityAEHostablePart
                                        implements IMultiblockAbilityPart {
 
     public final static String ITEM_BUFFER_TAG = "ItemBuffer";
     public final static String WORKING_TAG = "WorkingEnabled";
-    private boolean workingEnabled;
+    private boolean workingEnabled = true;
     private SerializableItemList internalBuffer;
 
     public MetaTileEntityMEOutputBus(ResourceLocation metaTileEntityId) {
-        super(metaTileEntityId, GTValues.UHV, true);
-        this.workingEnabled = true;
+        super(metaTileEntityId, GTValues.EV, true, IItemStorageChannel.class);
     }
 
     @Override
@@ -70,21 +64,18 @@ protected void initializeInventory() {
     @Override
     public void update() {
         super.update();
-        if (!getWorld().isRemote && this.workingEnabled && this.shouldSyncME()) {
-            if (this.updateMEStatus()) {
-                if (!this.internalBuffer.isEmpty()) {
-                    try {
-                        IMEMonitor aeNetwork = this.getProxy().getStorage().getInventory(ITEM_NET);
-                        for (IAEItemStack item : this.internalBuffer) {
-                            IAEItemStack notInserted = aeNetwork.injectItems(item.copy(), Actionable.MODULATE,
-                                    this.getActionSource());
-                            if (notInserted != null && notInserted.getStackSize() > 0) {
-                                item.setStackSize(notInserted.getStackSize());
-                            } else {
-                                item.reset();
-                            }
-                        }
-                    } catch (GridAccessException ignore) {}
+        if (!getWorld().isRemote && this.workingEnabled && this.shouldSyncME() && this.updateMEStatus()) {
+            if (this.internalBuffer.isEmpty()) return;
+
+            IMEMonitor monitor = getMonitor();
+            if (monitor == null) return;
+
+            for (IAEItemStack item : this.internalBuffer) {
+                IAEItemStack notInserted = monitor.injectItems(item.copy(), Actionable.MODULATE, getActionSource());
+                if (notInserted != null && notInserted.getStackSize() > 0) {
+                    item.setStackSize(notInserted.getStackSize());
+                } else {
+                    item.reset();
                 }
             }
         }
@@ -92,12 +83,12 @@ public void update() {
 
     @Override
     public void onRemoval() {
-        try {
-            IMEMonitor aeNetwork = this.getProxy().getStorage().getInventory(ITEM_NET);
+        IMEMonitor monitor = getMonitor();
+        if (monitor != null) {
             for (IAEItemStack item : this.internalBuffer) {
-                aeNetwork.injectItems(item.copy(), Actionable.MODULATE, this.getActionSource());
+                monitor.injectItems(item.copy(), Actionable.MODULATE, this.getActionSource());
             }
-        } catch (GridAccessException ignore) {}
+        }
         super.onRemoval();
     }
 
@@ -115,7 +106,7 @@ protected ModularUI createUI(EntityPlayer entityPlayer) {
         builder.dynamicLabel(10, 15, () -> this.isOnline ?
                 I18n.format("gregtech.gui.me_network.online") :
                 I18n.format("gregtech.gui.me_network.offline"),
-                0xFFFFFFFF);
+                0x404040);
         builder.label(10, 25, "gregtech.gui.waiting_list", 0xFFFFFFFF);
         builder.widget(new AEItemGridWidget(10, 35, 3, this.internalBuffer));
 
@@ -180,7 +171,11 @@ public void readFromNBT(NBTTagCompound data) {
     public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline) {
         super.renderMetaTileEntity(renderState, translation, pipeline);
         if (this.shouldRenderOverlay()) {
-            Textures.ME_OUTPUT_BUS.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            if (isOnline) {
+                Textures.ME_OUTPUT_BUS_ACTIVE.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            } else {
+                Textures.ME_OUTPUT_BUS.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            }
         }
     }
 
@@ -190,7 +185,7 @@ public void addInformation(ItemStack stack, @Nullable World player, @NotNull Lis
         super.addInformation(stack, player, tooltip, advanced);
         tooltip.add(I18n.format("gregtech.machine.item_bus.export.tooltip"));
         tooltip.add(I18n.format("gregtech.machine.me.item_export.tooltip"));
-        tooltip.add(I18n.format("gregtech.machine.me.export.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.item_export.tooltip.2"));
         tooltip.add(I18n.format("gregtech.universal.enabled"));
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java
index c19e7e1bb51..f22c489909b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java
@@ -32,10 +32,10 @@
 
 import appeng.api.config.Actionable;
 import appeng.api.storage.IMEMonitor;
+import appeng.api.storage.channels.IFluidStorageChannel;
 import appeng.api.storage.data.IAEFluidStack;
 import appeng.api.storage.data.IItemList;
 import appeng.fluids.util.AEFluidStack;
-import appeng.me.GridAccessException;
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
@@ -45,22 +45,16 @@
 import java.util.ArrayList;
 import java.util.List;
 
-/**
- * @Author GlodBlock
- * @Description The Output Hatch that can directly send its contents to ME storage network.
- * @Date 2023/4/19-1:18
- */
-public class MetaTileEntityMEOutputHatch extends MetaTileEntityAEHostablePart
+public class MetaTileEntityMEOutputHatch extends MetaTileEntityAEHostablePart
                                          implements IMultiblockAbilityPart {
 
     public final static String FLUID_BUFFER_TAG = "FluidBuffer";
     public final static String WORKING_TAG = "WorkingEnabled";
-    private boolean workingEnabled;
+    private boolean workingEnabled = true;
     private SerializableFluidList internalBuffer;
 
     public MetaTileEntityMEOutputHatch(ResourceLocation metaTileEntityId) {
-        super(metaTileEntityId, GTValues.UHV, true);
-        this.workingEnabled = true;
+        super(metaTileEntityId, GTValues.EV, true, IFluidStorageChannel.class);
     }
 
     @Override
@@ -72,21 +66,18 @@ protected void initializeInventory() {
     @Override
     public void update() {
         super.update();
-        if (!getWorld().isRemote && this.workingEnabled && this.shouldSyncME()) {
-            if (this.updateMEStatus()) {
-                if (!this.internalBuffer.isEmpty()) {
-                    try {
-                        IMEMonitor aeNetwork = this.getProxy().getStorage().getInventory(FLUID_NET);
-                        for (IAEFluidStack fluid : this.internalBuffer) {
-                            IAEFluidStack notInserted = aeNetwork.injectItems(fluid.copy(), Actionable.MODULATE,
-                                    this.getActionSource());
-                            if (notInserted != null && notInserted.getStackSize() > 0) {
-                                fluid.setStackSize(notInserted.getStackSize());
-                            } else {
-                                fluid.reset();
-                            }
-                        }
-                    } catch (GridAccessException ignore) {}
+        if (!getWorld().isRemote && this.workingEnabled && this.shouldSyncME() && updateMEStatus()) {
+            if (this.internalBuffer.isEmpty()) return;
+
+            IMEMonitor monitor = getMonitor();
+            if (monitor == null) return;
+
+            for (IAEFluidStack fluid : this.internalBuffer) {
+                IAEFluidStack notInserted = monitor.injectItems(fluid.copy(), Actionable.MODULATE, getActionSource());
+                if (notInserted != null && notInserted.getStackSize() > 0) {
+                    fluid.setStackSize(notInserted.getStackSize());
+                } else {
+                    fluid.reset();
                 }
             }
         }
@@ -94,12 +85,12 @@ public void update() {
 
     @Override
     public void onRemoval() {
-        try {
-            IMEMonitor aeNetwork = this.getProxy().getStorage().getInventory(FLUID_NET);
-            for (IAEFluidStack fluid : this.internalBuffer) {
-                aeNetwork.injectItems(fluid.copy(), Actionable.MODULATE, this.getActionSource());
-            }
-        } catch (GridAccessException ignore) {}
+        IMEMonitor monitor = getMonitor();
+        if (monitor == null) return;
+
+        for (IAEFluidStack fluid : this.internalBuffer) {
+            monitor.injectItems(fluid.copy(), Actionable.MODULATE, this.getActionSource());
+        }
         super.onRemoval();
     }
 
@@ -117,7 +108,7 @@ protected ModularUI createUI(EntityPlayer entityPlayer) {
         builder.dynamicLabel(10, 15, () -> this.isOnline ?
                 I18n.format("gregtech.gui.me_network.online") :
                 I18n.format("gregtech.gui.me_network.offline"),
-                0xFFFFFFFF);
+                0x404040);
         builder.label(10, 25, "gregtech.gui.waiting_list", 0xFFFFFFFF);
         builder.widget(new AEFluidGridWidget(10, 35, 3, this.internalBuffer));
 
@@ -182,7 +173,11 @@ public void readFromNBT(NBTTagCompound data) {
     public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline) {
         super.renderMetaTileEntity(renderState, translation, pipeline);
         if (this.shouldRenderOverlay()) {
-            Textures.ME_OUTPUT_HATCH.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            if (isOnline) {
+                Textures.ME_OUTPUT_HATCH_ACTIVE.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            } else {
+                Textures.ME_OUTPUT_HATCH.renderSided(getFrontFacing(), renderState, translation, pipeline);
+            }
         }
     }
 
@@ -192,7 +187,7 @@ public void addInformation(ItemStack stack, @Nullable World player, @NotNull Lis
         super.addInformation(stack, player, tooltip, advanced);
         tooltip.add(I18n.format("gregtech.machine.fluid_hatch.export.tooltip"));
         tooltip.add(I18n.format("gregtech.machine.me.fluid_export.tooltip"));
-        tooltip.add(I18n.format("gregtech.machine.me.export.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.fluid_export.tooltip.2"));
         tooltip.add(I18n.format("gregtech.universal.enabled"));
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java
new file mode 100644
index 00000000000..f8bd7a5850a
--- /dev/null
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java
@@ -0,0 +1,442 @@
+package gregtech.common.metatileentities.multi.multiblockpart.appeng;
+
+import gregtech.api.GTValues;
+import gregtech.api.gui.GuiTextures;
+import gregtech.api.gui.ModularUI;
+import gregtech.api.gui.widgets.ImageCycleButtonWidget;
+import gregtech.api.metatileentity.MetaTileEntity;
+import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+import gregtech.api.metatileentity.multiblock.MultiblockAbility;
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEItemList;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEItemSlot;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedItemStack;
+
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.TextComponentTranslation;
+import net.minecraft.world.World;
+
+import appeng.api.config.Actionable;
+import appeng.api.storage.IMEMonitor;
+import appeng.api.storage.data.IAEItemStack;
+import appeng.api.storage.data.IItemList;
+import codechicken.lib.raytracer.CuboidRayTraceResult;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import static gregtech.api.capability.GregtechDataCodes.UPDATE_AUTO_PULL;
+
+public class MetaTileEntityMEStockingBus extends MetaTileEntityMEInputBus {
+
+    private static final int CONFIG_SIZE = 16;
+    private boolean autoPull;
+    private Predicate autoPullTest;
+
+    public MetaTileEntityMEStockingBus(ResourceLocation metaTileEntityId) {
+        super(metaTileEntityId, GTValues.LuV);
+        this.autoPullTest = $ -> false;
+    }
+
+    @Override
+    public MetaTileEntity createMetaTileEntity(IGregTechTileEntity iGregTechTileEntity) {
+        return new MetaTileEntityMEStockingBus(metaTileEntityId);
+    }
+
+    @Override
+    protected ExportOnlyAEStockingItemList getAEItemHandler() {
+        if (this.aeItemHandler == null) {
+            this.aeItemHandler = new ExportOnlyAEStockingItemList(this, CONFIG_SIZE, getController());
+        }
+        return (ExportOnlyAEStockingItemList) this.aeItemHandler;
+    }
+
+    @Override
+    public void update() {
+        super.update();
+        if (!getWorld().isRemote && isWorkingEnabled() && autoPull && getOffsetTimer() % 100 == 0) {
+            refreshList();
+            syncME();
+        }
+    }
+
+    // Update the visual display for the fake items. This also is important for the item handler's
+    // getStackInSlot() method, as it uses the cached items set here.
+    @Override
+    protected void syncME() {
+        IMEMonitor monitor = super.getMonitor();
+        if (monitor == null) return;
+
+        for (ExportOnlyAEStockingItemSlot slot : this.getAEItemHandler().getInventory()) {
+            if (slot.getConfig() == null) {
+                slot.setStack(null);
+            } else {
+                // Try to fill the slot
+                IAEItemStack request;
+                if (slot.getConfig() instanceof WrappedItemStack wis) {
+                    request = wis.getAEStack();
+                } else {
+                    request = slot.getConfig().copy();
+                }
+                request.setStackSize(Integer.MAX_VALUE);
+                IAEItemStack result = monitor.extractItems(request, Actionable.SIMULATE, getActionSource());
+                slot.setStack(result);
+            }
+        }
+    }
+
+    @Override
+    protected void flushInventory() {
+        // no-op, nothing to send back to the network
+    }
+
+    @Override
+    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+        super.addToMultiBlock(controllerBase);
+        // ensure that no other stocking bus on this multiblock is configured to hold the same item.
+        // that we have in our own bus.
+        this.autoPullTest = stack -> !this.testConfiguredInOtherBus(stack);
+        // also ensure that our current config is valid given other inputs
+        validateConfig();
+    }
+
+    @Override
+    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+        // block auto-pull from working when not in a formed multiblock
+        this.autoPullTest = $ -> false;
+        if (this.autoPull) {
+            // may as well clear if we are auto-pull, no reason to preserve the config
+            this.getAEItemHandler().clearConfig();
+        }
+        super.removeFromMultiBlock(controllerBase);
+    }
+
+    @Override
+    public void onDistinctChange(boolean newValue) {
+        super.onDistinctChange(newValue);
+        if (!getWorld().isRemote && !newValue) {
+            // Ensure that our configured items won't match any other buses in the multiblock.
+            // Needed since we allow duplicates in distinct mode on, but not off
+            validateConfig();
+        }
+    }
+
+    /**
+     * Test for if any of our configured items are in another stocking bus on the multi
+     * we are attached to. Prevents dupes in certain situations.
+     */
+    private void validateConfig() {
+        for (var slot : this.getAEItemHandler().getInventory()) {
+            if (slot.getConfig() != null) {
+                ItemStack configuredStack = slot.getConfig().createItemStack();
+                if (testConfiguredInOtherBus(configuredStack)) {
+                    slot.setConfig(null);
+                    slot.setStock(null);
+                }
+            }
+        }
+    }
+
+    /**
+     * @return True if the passed stack is found as a configuration in any other stocking buses on the multiblock.
+     */
+    private boolean testConfiguredInOtherBus(ItemStack stack) {
+        if (stack == null || stack.isEmpty()) return false;
+        MultiblockControllerBase controller = getController();
+        if (controller == null) return false;
+
+        // In distinct mode, we don't need to check other buses since only one bus can run a recipe at a time.
+        if (!(controller instanceof RecipeMapMultiblockController rmmc) || !rmmc.isDistinct()) {
+            // Otherwise, we need to test for if the item is configured
+            // in any stocking bus in the multi (besides ourselves).
+            var abilityList = controller.getAbilities(MultiblockAbility.IMPORT_ITEMS);
+            for (var ability : abilityList) {
+                if (ability instanceof ExportOnlyAEStockingItemList aeList) {
+                    // We don't need to check for ourselves, as this case is handled elsewhere.
+                    if (aeList == this.aeItemHandler) continue;
+                    if (aeList.hasStackInConfig(stack, false)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private void setAutoPull(boolean autoPull) {
+        this.autoPull = autoPull;
+        markDirty();
+        if (!getWorld().isRemote) {
+            if (!this.autoPull) {
+                this.getAEItemHandler().clearConfig();
+            } else if (updateMEStatus()) {
+                this.refreshList();
+                syncME();
+            }
+            writeCustomData(UPDATE_AUTO_PULL, buf -> buf.writeBoolean(this.autoPull));
+        }
+    }
+
+    /**
+     * Refresh the configuration list in auto-pull mode.
+     * Sets the config to the first 16 valid items found in the network.
+     */
+    private void refreshList() {
+        IMEMonitor monitor = getMonitor();
+        if (monitor == null) {
+            clearInventory(0);
+            return;
+        }
+
+        IItemList storageList = monitor.getStorageList();
+        if (storageList == null) {
+            clearInventory(0);
+            return;
+        }
+
+        int index = 0;
+        for (IAEItemStack stack : storageList) {
+            if (index >= CONFIG_SIZE) break;
+            if (stack.getStackSize() == 0) continue;
+            stack = monitor.extractItems(stack, Actionable.SIMULATE, getActionSource());
+            if (stack == null || stack.getStackSize() == 0) continue;
+
+            ItemStack itemStack = stack.createItemStack();
+            if (itemStack == null || itemStack.isEmpty()) continue;
+            // Ensure that it is valid to configure with this stack
+            if (autoPullTest != null && !autoPullTest.test(itemStack)) continue;
+            IAEItemStack selectedStack = WrappedItemStack.fromItemStack(itemStack);
+            if (selectedStack == null) continue;
+            IAEItemStack configStack = selectedStack.copy().setStackSize(1);
+            var slot = this.getAEItemHandler().getInventory()[index];
+            slot.setConfig(configStack);
+            slot.setStack(selectedStack);
+            index++;
+        }
+
+        clearInventory(index);
+    }
+
+    private void clearInventory(int startIndex) {
+        for (int i = startIndex; i < CONFIG_SIZE; i++) {
+            var slot = this.getAEItemHandler().getInventory()[i];
+            slot.setConfig(null);
+            slot.setStack(null);
+        }
+    }
+
+    @Override
+    public void receiveCustomData(int dataId, PacketBuffer buf) {
+        super.receiveCustomData(dataId, buf);
+        if (dataId == UPDATE_AUTO_PULL) {
+            this.autoPull = buf.readBoolean();
+        }
+    }
+
+    @Override
+    protected ModularUI.Builder createUITemplate(EntityPlayer player) {
+        ModularUI.Builder builder = super.createUITemplate(player);
+        builder.widget(new ImageCycleButtonWidget(7 + 18 * 4 + 1, 26, 16, 16, GuiTextures.BUTTON_AUTO_PULL,
+                () -> autoPull, this::setAutoPull).setTooltipHoverString("gregtech.gui.me_bus.auto_pull_button"));
+        return builder;
+    }
+
+    @Override
+    public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
+                                      CuboidRayTraceResult hitResult) {
+        if (!getWorld().isRemote) {
+            setAutoPull(!autoPull);
+            if (autoPull) {
+                playerIn.sendStatusMessage(
+                        new TextComponentTranslation("gregtech.machine.me.stocking_auto_pull_enabled"), false);
+            } else {
+                playerIn.sendStatusMessage(
+                        new TextComponentTranslation("gregtech.machine.me.stocking_auto_pull_disabled"), false);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public NBTTagCompound writeToNBT(NBTTagCompound data) {
+        super.writeToNBT(data);
+        data.setBoolean("AutoPull", autoPull);
+        return data;
+    }
+
+    @Override
+    public void readFromNBT(NBTTagCompound data) {
+        super.readFromNBT(data);
+        this.autoPull = data.getBoolean("AutoPull");
+    }
+
+    @Override
+    public void writeInitialSyncData(PacketBuffer buf) {
+        super.writeInitialSyncData(buf);
+        buf.writeBoolean(autoPull);
+    }
+
+    @Override
+    public void receiveInitialSyncData(PacketBuffer buf) {
+        super.receiveInitialSyncData(buf);
+        this.autoPull = buf.readBoolean();
+    }
+
+    private static class ExportOnlyAEStockingItemSlot extends ExportOnlyAEItemSlot {
+
+        private final MetaTileEntityMEStockingBus holder;
+
+        public ExportOnlyAEStockingItemSlot(IAEItemStack config, IAEItemStack stock,
+                                            MetaTileEntityMEStockingBus holder) {
+            super(config, stock);
+            this.holder = holder;
+        }
+
+        public ExportOnlyAEStockingItemSlot(MetaTileEntityMEStockingBus holder) {
+            super();
+            this.holder = holder;
+        }
+
+        @Override
+        public ExportOnlyAEStockingItemSlot copy() {
+            return new ExportOnlyAEStockingItemSlot(
+                    this.config == null ? null : this.config.copy(),
+                    this.stock == null ? null : this.stock.copy(),
+                    this.holder);
+        }
+
+        @Override
+        public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate) {
+            if (slot == 0 && this.stock != null) {
+                if (this.config != null) {
+                    // Extract the items from the real net to either validate (simulate)
+                    // or extract (modulate) when this is called
+                    IMEMonitor monitor = holder.getMonitor();
+                    if (monitor == null) return ItemStack.EMPTY;
+
+                    Actionable action = simulate ? Actionable.SIMULATE : Actionable.MODULATE;
+                    IAEItemStack request;
+                    if (this.config instanceof WrappedItemStack wis) {
+                        request = wis.getAEStack();
+                    } else {
+                        request = this.config.copy();
+                    }
+                    request.setStackSize(amount);
+
+                    IAEItemStack result = monitor.extractItems(request, action, holder.getActionSource());
+                    if (result != null) {
+                        int extracted = (int) Math.min(result.getStackSize(), amount);
+                        this.stock.decStackSize(extracted); // may as well update the display here
+                        if (this.trigger != null) {
+                            this.trigger.accept(0);
+                        }
+                        if (extracted != 0) {
+                            ItemStack resultStack = this.config.createItemStack();
+                            resultStack.setCount(extracted);
+                            return resultStack;
+                        }
+                    }
+                }
+            }
+            return ItemStack.EMPTY;
+        }
+    }
+
+    @Override
+    public void addInformation(ItemStack stack, @Nullable World player, @NotNull List tooltip,
+                               boolean advanced) {
+        tooltip.add(I18n.format("gregtech.machine.item_bus.import.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.stocking_item.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me_import_item_hatch.configs.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.copy_paste.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.stocking_item.tooltip.2"));
+        tooltip.add(I18n.format("gregtech.universal.enabled"));
+    }
+
+    @Override
+    protected NBTTagCompound writeConfigToTag() {
+        if (!autoPull) {
+            NBTTagCompound tag = super.writeConfigToTag();
+            tag.setBoolean("AutoPull", false);
+            return tag;
+        }
+        // if in auto-pull, no need to write actual configured slots, but still need to write the ghost circuit
+        NBTTagCompound tag = new NBTTagCompound();
+        tag.setBoolean("AutoPull", true);
+        tag.setByte("GhostCircuit", (byte) this.circuitInventory.getCircuitValue());
+        return tag;
+    }
+
+    @Override
+    protected void readConfigFromTag(NBTTagCompound tag) {
+        if (tag.getBoolean("AutoPull")) {
+            // if being set to auto-pull, no need to read the configured slots
+            this.setAutoPull(true);
+            this.setGhostCircuitConfig(tag.getByte("GhostCircuit"));
+            return;
+        }
+        // set auto pull first to avoid issues with clearing the config after reading from the data stick
+        this.setAutoPull(false);
+        super.readConfigFromTag(tag);
+    }
+
+    private static class ExportOnlyAEStockingItemList extends ExportOnlyAEItemList {
+
+        private final MetaTileEntityMEStockingBus holder;
+
+        public ExportOnlyAEStockingItemList(MetaTileEntityMEStockingBus holder, int slots,
+                                            MetaTileEntity entityToNotify) {
+            super(holder, slots, entityToNotify);
+            this.holder = holder;
+        }
+
+        @Override
+        protected void createInventory(MetaTileEntity holder) {
+            if (!(holder instanceof MetaTileEntityMEStockingBus stocking)) {
+                throw new IllegalArgumentException("Cannot create Stocking Item List for nonstocking MetaTileEntity!");
+            }
+            this.inventory = new ExportOnlyAEStockingItemSlot[size];
+            for (int i = 0; i < size; i++) {
+                this.inventory[i] = new ExportOnlyAEStockingItemSlot(stocking);
+            }
+            for (ExportOnlyAEItemSlot slot : this.inventory) {
+                slot.setTrigger(this::onContentsChanged);
+            }
+        }
+
+        @Override
+        public ExportOnlyAEStockingItemSlot[] getInventory() {
+            return (ExportOnlyAEStockingItemSlot[]) super.getInventory();
+        }
+
+        @Override
+        public boolean isStocking() {
+            return true;
+        }
+
+        @Override
+        public boolean isAutoPull() {
+            return holder.autoPull;
+        }
+
+        @Override
+        public boolean hasStackInConfig(ItemStack stack, boolean checkExternal) {
+            boolean inThisBus = super.hasStackInConfig(stack, false);
+            if (inThisBus) return true;
+            if (checkExternal) {
+                return holder.testConfiguredInOtherBus(stack);
+            }
+            return false;
+        }
+    }
+}
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java
new file mode 100644
index 00000000000..fcc9e1e7d12
--- /dev/null
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java
@@ -0,0 +1,401 @@
+package gregtech.common.metatileentities.multi.multiblockpart.appeng;
+
+import gregtech.api.GTValues;
+import gregtech.api.gui.GuiTextures;
+import gregtech.api.gui.ModularUI;
+import gregtech.api.gui.widgets.ImageCycleButtonWidget;
+import gregtech.api.metatileentity.MetaTileEntity;
+import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+import gregtech.api.metatileentity.multiblock.MultiblockAbility;
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEFluidList;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.slot.ExportOnlyAEFluidSlot;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedFluidStack;
+
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.text.TextComponentTranslation;
+import net.minecraft.world.World;
+import net.minecraftforge.fluids.FluidStack;
+
+import appeng.api.config.Actionable;
+import appeng.api.storage.IMEMonitor;
+import appeng.api.storage.data.IAEFluidStack;
+import appeng.api.storage.data.IItemList;
+import codechicken.lib.raytracer.CuboidRayTraceResult;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import static gregtech.api.capability.GregtechDataCodes.UPDATE_AUTO_PULL;
+
+public class MetaTileEntityMEStockingHatch extends MetaTileEntityMEInputHatch {
+
+    private static final int CONFIG_SIZE = 16;
+    private boolean autoPull;
+    private Predicate autoPullTest;
+
+    public MetaTileEntityMEStockingHatch(ResourceLocation metaTileEntityId) {
+        super(metaTileEntityId, GTValues.LuV);
+        this.autoPullTest = $ -> false;
+    }
+
+    @Override
+    public MetaTileEntity createMetaTileEntity(IGregTechTileEntity iGregTechTileEntity) {
+        return new MetaTileEntityMEStockingHatch(metaTileEntityId);
+    }
+
+    @Override
+    protected ExportOnlyAEStockingFluidList getAEFluidHandler() {
+        if (this.aeFluidHandler == null) {
+            this.aeFluidHandler = new ExportOnlyAEStockingFluidList(this, CONFIG_SIZE, getController());
+        }
+        return (ExportOnlyAEStockingFluidList) this.aeFluidHandler;
+    }
+
+    @Override
+    public void update() {
+        super.update();
+        if (!getWorld().isRemote && isWorkingEnabled() && autoPull && getOffsetTimer() % 100 == 0) {
+            refreshList();
+            syncME();
+        }
+    }
+
+    @Override
+    protected void syncME() {
+        IMEMonitor monitor = super.getMonitor();
+        if (monitor == null) return;
+
+        for (ExportOnlyAEStockingFluidSlot slot : this.getAEFluidHandler().getInventory()) {
+            if (slot.getConfig() == null) {
+                slot.setStack(null);
+            } else {
+                // Try to fill the slot
+                IAEFluidStack request;
+                if (slot.getConfig() instanceof WrappedFluidStack wfs) {
+                    request = wfs.getAEStack();
+                } else {
+                    request = slot.getConfig().copy();
+                }
+                request.setStackSize(Integer.MAX_VALUE);
+                IAEFluidStack result = monitor.extractItems(request, Actionable.SIMULATE, getActionSource());
+                slot.setStack(result);
+            }
+        }
+    }
+
+    @Override
+    protected void flushInventory() {
+        // no-op, nothing to send back to the network
+    }
+
+    @Override
+    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+        super.addToMultiBlock(controllerBase);
+        this.autoPullTest = stack -> !this.testConfiguredInOtherHatch(stack);
+        validateConfig();
+    }
+
+    @Override
+    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+        this.autoPullTest = $ -> false;
+        if (this.autoPull) {
+            this.getAEFluidHandler().clearConfig();
+        }
+        super.removeFromMultiBlock(controllerBase);
+    }
+
+    private void validateConfig() {
+        for (var slot : this.getAEFluidHandler().getInventory()) {
+            if (slot.getConfig() != null) {
+                FluidStack configuredStack = slot.getConfig().getFluidStack();
+                if (testConfiguredInOtherHatch(configuredStack)) {
+                    slot.setConfig(null);
+                    slot.setStock(null);
+                }
+            }
+        }
+    }
+
+    private boolean testConfiguredInOtherHatch(FluidStack stack) {
+        if (stack == null) return false;
+        MultiblockControllerBase controller = getController();
+        if (controller == null) return false;
+
+        var abilityList = controller.getAbilities(MultiblockAbility.IMPORT_FLUIDS);
+        for (var ability : abilityList) {
+            if (ability instanceof ExportOnlyAEStockingFluidSlot aeSlot) {
+                if (aeSlot.getConfig() == null) continue;
+                if (getAEFluidHandler().ownsSlot(aeSlot)) continue;
+                if (aeSlot.getConfig().getFluid().equals(stack.getFluid())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void setAutoPull(boolean autoPull) {
+        this.autoPull = autoPull;
+        markDirty();
+        if (!getWorld().isRemote) {
+            if (!this.autoPull) {
+                this.getAEFluidHandler().clearConfig();
+            } else if (updateMEStatus()) {
+                this.refreshList();
+                syncME();
+            }
+            writeCustomData(UPDATE_AUTO_PULL, buf -> buf.writeBoolean(this.autoPull));
+        }
+    }
+
+    private void refreshList() {
+        IMEMonitor monitor = getMonitor();
+        if (monitor == null) {
+            clearInventory(0);
+            return;
+        }
+
+        IItemList storageList = monitor.getStorageList();
+        if (storageList == null) {
+            clearInventory(0);
+            return;
+        }
+
+        int index = 0;
+        for (IAEFluidStack stack : storageList) {
+            if (index >= CONFIG_SIZE) break;
+            if (stack.getStackSize() == 0) continue;
+            stack = monitor.extractItems(stack, Actionable.SIMULATE, getActionSource());
+            if (stack == null || stack.getStackSize() == 0) continue;
+
+            FluidStack fluidStack = stack.getFluidStack();
+            if (fluidStack == null) continue;
+            if (autoPullTest != null && !autoPullTest.test(fluidStack)) continue;
+            IAEFluidStack selectedStack = WrappedFluidStack.fromFluidStack(fluidStack);
+            IAEFluidStack configStack = selectedStack.copy().setStackSize(1);
+            var slot = this.getAEFluidHandler().getInventory()[index];
+            slot.setConfig(configStack);
+            slot.setStack(selectedStack);
+            index++;
+        }
+
+        clearInventory(index);
+    }
+
+    private void clearInventory(int startIndex) {
+        for (int i = startIndex; i < CONFIG_SIZE; i++) {
+            var slot = this.getAEFluidHandler().getInventory()[i];
+            slot.setConfig(null);
+            slot.setStack(null);
+        }
+    }
+
+    @Override
+    public void receiveCustomData(int dataId, PacketBuffer buf) {
+        super.receiveCustomData(dataId, buf);
+        if (dataId == UPDATE_AUTO_PULL) {
+            this.autoPull = buf.readBoolean();
+        }
+    }
+
+    @Override
+    protected ModularUI.Builder createUITemplate(EntityPlayer player) {
+        ModularUI.Builder builder = super.createUITemplate(player);
+        builder.widget(new ImageCycleButtonWidget(7 + 18 * 4 + 1, 26, 16, 16, GuiTextures.BUTTON_AUTO_PULL,
+                () -> autoPull, this::setAutoPull).setTooltipHoverString("gregtech.gui.me_bus.auto_pull_button"));
+        return builder;
+    }
+
+    @Override
+    public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
+                                      CuboidRayTraceResult hitResult) {
+        if (!getWorld().isRemote) {
+            setAutoPull(!autoPull);
+            if (autoPull) {
+                playerIn.sendStatusMessage(
+                        new TextComponentTranslation("gregtech.machine.me.stocking_auto_pull_enabled"), false);
+            } else {
+                playerIn.sendStatusMessage(
+                        new TextComponentTranslation("gregtech.machine.me.stocking_auto_pull_disabled"), false);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public NBTTagCompound writeToNBT(NBTTagCompound data) {
+        super.writeToNBT(data);
+        data.setBoolean("AutoPull", autoPull);
+        return data;
+    }
+
+    @Override
+    public void readFromNBT(NBTTagCompound data) {
+        super.readFromNBT(data);
+        this.autoPull = data.getBoolean("AutoPull");
+    }
+
+    @Override
+    public void writeInitialSyncData(PacketBuffer buf) {
+        super.writeInitialSyncData(buf);
+        buf.writeBoolean(autoPull);
+    }
+
+    @Override
+    public void receiveInitialSyncData(PacketBuffer buf) {
+        super.receiveInitialSyncData(buf);
+        this.autoPull = buf.readBoolean();
+    }
+
+    @Override
+    public void addInformation(ItemStack stack, @Nullable World player, @NotNull List tooltip,
+                               boolean advanced) {
+        tooltip.add(I18n.format("gregtech.machine.fluid_hatch.import.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.stocking_fluid.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me_import_fluid_hatch.configs.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.copy_paste.tooltip"));
+        tooltip.add(I18n.format("gregtech.machine.me.stocking_fluid.tooltip.2"));
+        tooltip.add(I18n.format("gregtech.universal.enabled"));
+    }
+
+    @Override
+    protected NBTTagCompound writeConfigToTag() {
+        if (!autoPull) {
+            NBTTagCompound tag = super.writeConfigToTag();
+            tag.setBoolean("AutoPull", false);
+            return tag;
+        }
+        // if in auto-pull, no need to write actual configured slots, but still need to write the ghost circuit
+        NBTTagCompound tag = new NBTTagCompound();
+        tag.setBoolean("AutoPull", true);
+        return tag;
+    }
+
+    @Override
+    protected void readConfigFromTag(NBTTagCompound tag) {
+        if (tag.getBoolean("AutoPull")) {
+            // if being set to auto-pull, no need to read the configured slots
+            this.setAutoPull(true);
+            return;
+        }
+        // set auto pull first to avoid issues with clearing the config after reading from the data stick
+        this.setAutoPull(false);
+        super.readConfigFromTag(tag);
+    }
+
+    private static class ExportOnlyAEStockingFluidSlot extends ExportOnlyAEFluidSlot {
+
+        public ExportOnlyAEStockingFluidSlot(MetaTileEntityMEStockingHatch holder, IAEFluidStack config,
+                                             IAEFluidStack stock, MetaTileEntity entityToNotify) {
+            super(holder, config, stock, entityToNotify);
+        }
+
+        public ExportOnlyAEStockingFluidSlot(MetaTileEntityMEStockingHatch holder, MetaTileEntity entityToNotify) {
+            super(holder, entityToNotify);
+        }
+
+        @Override
+        protected MetaTileEntityMEStockingHatch getHolder() {
+            return (MetaTileEntityMEStockingHatch) super.getHolder();
+        }
+
+        @Override
+        public ExportOnlyAEFluidSlot copy() {
+            return new ExportOnlyAEStockingFluidSlot(
+                    this.getHolder(),
+                    this.config == null ? null : this.config.copy(),
+                    this.stock == null ? null : this.stock.copy(),
+                    null);
+        }
+
+        @Override
+        public @Nullable FluidStack drain(int maxDrain, boolean doDrain) {
+            if (this.stock == null) {
+                return null;
+            }
+            if (this.config != null) {
+                IMEMonitor monitor = getHolder().getMonitor();
+                if (monitor == null) return null;
+
+                Actionable action = doDrain ? Actionable.MODULATE : Actionable.SIMULATE;
+                IAEFluidStack request;
+                if (this.config instanceof WrappedFluidStack wfs) {
+                    request = wfs.getAEStack();
+                } else {
+                    request = this.config.copy();
+                }
+                request.setStackSize(maxDrain);
+
+                IAEFluidStack result = monitor.extractItems(request, action, getHolder().getActionSource());
+                if (result != null) {
+                    int extracted = (int) Math.min(result.getStackSize(), maxDrain);
+                    this.stock.decStackSize(extracted);
+                    trigger();
+                    if (extracted != 0) {
+                        FluidStack resultStack = this.config.getFluidStack();
+                        resultStack.amount = extracted;
+                        return resultStack;
+                    }
+                }
+            }
+            return null;
+        }
+    }
+
+    private static class ExportOnlyAEStockingFluidList extends ExportOnlyAEFluidList {
+
+        private final MetaTileEntityMEStockingHatch holder;
+
+        public ExportOnlyAEStockingFluidList(MetaTileEntityMEStockingHatch holder, int slots,
+                                             MetaTileEntity entityToNotify) {
+            super(holder, slots, entityToNotify);
+            this.holder = holder;
+        }
+
+        @Override
+        protected void createInventory(MetaTileEntity holder, MetaTileEntity entityToNotify) {
+            if (!(holder instanceof MetaTileEntityMEStockingHatch stocking)) {
+                throw new IllegalArgumentException("Cannot create Stocking Fluid List for nonstocking MetaTileEntity!");
+            }
+            this.inventory = new ExportOnlyAEStockingFluidSlot[size];
+            for (int i = 0; i < size; i++) {
+                this.inventory[i] = new ExportOnlyAEStockingFluidSlot(stocking, entityToNotify);
+            }
+        }
+
+        @Override
+        public ExportOnlyAEStockingFluidSlot[] getInventory() {
+            return (ExportOnlyAEStockingFluidSlot[]) super.getInventory();
+        }
+
+        @Override
+        public boolean isStocking() {
+            return true;
+        }
+
+        @Override
+        public boolean isAutoPull() {
+            return holder.autoPull;
+        }
+
+        @Override
+        public boolean hasStackInConfig(FluidStack stack, boolean checkExternal) {
+            boolean inThisHatch = super.hasStackInConfig(stack, false);
+            if (inThisHatch) return true;
+            if (checkExternal) {
+                return holder.testConfiguredInOtherHatch(stack);
+            }
+            return false;
+        }
+    }
+}
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEFluidList.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEFluidList.java
new file mode 100644
index 00000000000..fd4b40ea7e5
--- /dev/null
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEFluidList.java
@@ -0,0 +1,64 @@
+package gregtech.common.metatileentities.multi.multiblockpart.appeng.slot;
+
+import gregtech.api.metatileentity.MetaTileEntity;
+
+import net.minecraftforge.fluids.FluidStack;
+
+import appeng.api.storage.data.IAEFluidStack;
+
+public class ExportOnlyAEFluidList {
+
+    protected final int size;
+    protected ExportOnlyAEFluidSlot[] inventory;
+
+    public ExportOnlyAEFluidList(MetaTileEntity holder, int slots, MetaTileEntity entityToNotify) {
+        this.size = slots;
+        createInventory(holder, entityToNotify);
+    }
+
+    protected void createInventory(MetaTileEntity holder, MetaTileEntity entityToNotify) {
+        this.inventory = new ExportOnlyAEFluidSlot[size];
+        for (int i = 0; i < size; i++) {
+            this.inventory[i] = new ExportOnlyAEFluidSlot(holder, entityToNotify);
+        }
+    }
+
+    public ExportOnlyAEFluidSlot[] getInventory() {
+        return inventory;
+    }
+
+    public void clearConfig() {
+        for (var slot : inventory) {
+            slot.setConfig(null);
+            slot.setStock(null);
+        }
+    }
+
+    public boolean hasStackInConfig(FluidStack stack, boolean checkExternal) {
+        if (stack == null) return false;
+        for (ExportOnlyAEFluidSlot slot : inventory) {
+            IAEFluidStack config = slot.getConfig();
+            if (config != null && config.getFluid().equals(stack.getFluid())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isAutoPull() {
+        return false;
+    }
+
+    public boolean isStocking() {
+        return false;
+    }
+
+    public boolean ownsSlot(ExportOnlyAEFluidSlot testSlot) {
+        for (var slot : inventory) {
+            if (slot == testSlot) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEFluidSlot.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEFluidSlot.java
new file mode 100644
index 00000000000..73ad45f9ac4
--- /dev/null
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEFluidSlot.java
@@ -0,0 +1,192 @@
+package gregtech.common.metatileentities.multi.multiblockpart.appeng.slot;
+
+import gregtech.api.capability.INotifiableHandler;
+import gregtech.api.metatileentity.MetaTileEntity;
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedFluidStack;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.FluidTankInfo;
+import net.minecraftforge.fluids.IFluidTank;
+import net.minecraftforge.fluids.capability.FluidTankProperties;
+import net.minecraftforge.fluids.capability.IFluidHandler;
+import net.minecraftforge.fluids.capability.IFluidTankProperties;
+
+import appeng.api.storage.data.IAEFluidStack;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExportOnlyAEFluidSlot extends ExportOnlyAESlot
+                                   implements IFluidTank, INotifiableHandler, IFluidHandler {
+
+    private final List notifiableEntities = new ArrayList<>();
+    private MetaTileEntity holder;
+
+    public ExportOnlyAEFluidSlot(MetaTileEntity holder, IAEFluidStack config, IAEFluidStack stock, MetaTileEntity mte) {
+        super(config, stock);
+        this.holder = holder;
+        this.notifiableEntities.add(mte);
+    }
+
+    public ExportOnlyAEFluidSlot(MetaTileEntity holder, MetaTileEntity entityToNotify) {
+        this(holder, null, null, entityToNotify);
+    }
+
+    public ExportOnlyAEFluidSlot() {
+        super();
+    }
+
+    @Override
+    public IAEFluidStack requestStack() {
+        IAEFluidStack result = super.requestStack();
+        if (result instanceof WrappedFluidStack) {
+            return ((WrappedFluidStack) result).getAEStack();
+        } else {
+            return result;
+        }
+    }
+
+    @Override
+    public IAEFluidStack exceedStack() {
+        IAEFluidStack result = super.exceedStack();
+        if (result instanceof WrappedFluidStack) {
+            return ((WrappedFluidStack) result).getAEStack();
+        } else {
+            return result;
+        }
+    }
+
+    @Override
+    public void addStack(IAEFluidStack stack) {
+        if (this.stock == null) {
+            this.stock = WrappedFluidStack.fromFluidStack(stack.getFluidStack());
+        } else {
+            this.stock.add(stack);
+        }
+        trigger();
+    }
+
+    @Override
+    public void setStack(IAEFluidStack stack) {
+        if (this.stock == null && stack == null) {
+            return;
+        } else if (stack == null) {
+            this.stock = null;
+        } else if (this.stock == null || this.stock.getFluid() != stack.getFluid()) {
+            this.stock = WrappedFluidStack.fromFluidStack(stack.getFluidStack());
+        } else if (this.stock.getStackSize() != stack.getStackSize()) {
+            this.stock.setStackSize(stack.getStackSize());
+        } else return;
+        trigger();
+    }
+
+    @Override
+    public void deserializeNBT(NBTTagCompound nbt) {
+        if (nbt.hasKey(CONFIG_TAG)) {
+            this.config = WrappedFluidStack.fromNBT(nbt.getCompoundTag(CONFIG_TAG));
+        }
+        if (nbt.hasKey(STOCK_TAG)) {
+            this.stock = WrappedFluidStack.fromNBT(nbt.getCompoundTag(STOCK_TAG));
+        }
+    }
+
+    @Nullable
+    @Override
+    public FluidStack getFluid() {
+        if (this.stock != null && this.stock instanceof WrappedFluidStack) {
+            return ((WrappedFluidStack) this.stock).getDelegate();
+        }
+        return null;
+    }
+
+    @Override
+    public int getFluidAmount() {
+        return this.stock != null ? (int) this.stock.getStackSize() : 0;
+    }
+
+    @Override
+    public int getCapacity() {
+        // Its capacity is always 0.
+        return 0;
+    }
+
+    @Override
+    public FluidTankInfo getInfo() {
+        return new FluidTankInfo(this);
+    }
+
+    @Override
+    public IFluidTankProperties[] getTankProperties() {
+        return new IFluidTankProperties[] {
+                new FluidTankProperties(this.getFluid(), 0)
+        };
+    }
+
+    @Override
+    public int fill(FluidStack resource, boolean doFill) {
+        return 0;
+    }
+
+    @Nullable
+    @Override
+    public FluidStack drain(FluidStack resource, boolean doDrain) {
+        if (this.getFluid() != null && this.getFluid().isFluidEqual(resource)) {
+            return this.drain(resource.amount, doDrain);
+        }
+        return null;
+    }
+
+    @Nullable
+    @Override
+    public FluidStack drain(int maxDrain, boolean doDrain) {
+        if (this.stock == null) {
+            return null;
+        }
+        int drained = (int) Math.min(this.stock.getStackSize(), maxDrain);
+        FluidStack result = new FluidStack(this.stock.getFluid(), drained);
+        if (doDrain) {
+            this.stock.decStackSize(drained);
+            if (this.stock.getStackSize() == 0) {
+                this.stock = null;
+            }
+            trigger();
+        }
+        return result;
+    }
+
+    @Override
+    public void addNotifiableMetaTileEntity(MetaTileEntity metaTileEntity) {
+        this.notifiableEntities.add(metaTileEntity);
+    }
+
+    @Override
+    public void removeNotifiableMetaTileEntity(MetaTileEntity metaTileEntity) {
+        this.notifiableEntities.remove(metaTileEntity);
+    }
+
+    protected void trigger() {
+        for (MetaTileEntity metaTileEntity : this.notifiableEntities) {
+            if (metaTileEntity != null && metaTileEntity.isValid()) {
+                this.addToNotifiedList(metaTileEntity, this, false);
+            }
+        }
+        if (holder != null) {
+            holder.markDirty();
+        }
+    }
+
+    @Override
+    public ExportOnlyAEFluidSlot copy() {
+        return new ExportOnlyAEFluidSlot(
+                this.holder,
+                this.config == null ? null : this.config.copy(),
+                this.stock == null ? null : this.stock.copy(),
+                null);
+    }
+
+    protected MetaTileEntity getHolder() {
+        return holder;
+    }
+}
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEItemList.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEItemList.java
new file mode 100644
index 00000000000..995670eff15
--- /dev/null
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEItemList.java
@@ -0,0 +1,126 @@
+package gregtech.common.metatileentities.multi.multiblockpart.appeng.slot;
+
+import gregtech.api.capability.impl.NotifiableItemStackHandler;
+import gregtech.api.metatileentity.MetaTileEntity;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+
+import appeng.api.storage.data.IAEItemStack;
+import org.jetbrains.annotations.NotNull;
+
+public class ExportOnlyAEItemList extends NotifiableItemStackHandler {
+
+    protected final int size;
+    protected ExportOnlyAEItemSlot[] inventory;
+
+    public ExportOnlyAEItemList(MetaTileEntity holder, int slots, MetaTileEntity entityToNotify) {
+        super(holder, slots, entityToNotify, false);
+        this.size = slots;
+        createInventory(holder);
+    }
+
+    protected void createInventory(MetaTileEntity holder) {
+        this.inventory = new ExportOnlyAEItemSlot[size];
+        for (int i = 0; i < size; i++) {
+            this.inventory[i] = new ExportOnlyAEItemSlot();
+        }
+        for (ExportOnlyAEItemSlot slot : this.inventory) {
+            slot.setTrigger(this::onContentsChanged);
+        }
+    }
+
+    public ExportOnlyAEItemSlot[] getInventory() {
+        return inventory;
+    }
+
+    @Override
+    public void deserializeNBT(NBTTagCompound nbt) {
+        for (int index = 0; index < size; index++) {
+            if (nbt.hasKey("#" + index)) {
+                NBTTagCompound slotTag = nbt.getCompoundTag("#" + index);
+                this.inventory[index].deserializeNBT(slotTag);
+            }
+        }
+    }
+
+    @Override
+    public NBTTagCompound serializeNBT() {
+        NBTTagCompound nbt = new NBTTagCompound();
+        for (int index = 0; index < size; index++) {
+            NBTTagCompound slot = this.inventory[index].serializeNBT();
+            nbt.setTag("#" + index, slot);
+        }
+        return nbt;
+    }
+
+    @Override
+    public void setStackInSlot(int slot, @NotNull ItemStack stack) {
+        // NO-OP
+    }
+
+    @Override
+    public int getSlots() {
+        return size;
+    }
+
+    @NotNull
+    @Override
+    public ItemStack getStackInSlot(int slot) {
+        if (slot >= 0 && slot < size) {
+            return this.inventory[slot].getStackInSlot(0);
+        }
+        return ItemStack.EMPTY;
+    }
+
+    @NotNull
+    @Override
+    public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) {
+        return stack;
+    }
+
+    @NotNull
+    @Override
+    public ItemStack extractItem(int slot, int amount, boolean simulate) {
+        if (slot >= 0 && slot < size) {
+            return this.inventory[slot].extractItem(0, amount, simulate);
+        }
+        return ItemStack.EMPTY;
+    }
+
+    @Override
+    public int getSlotLimit(int slot) {
+        return Integer.MAX_VALUE;
+    }
+
+    @Override
+    protected int getStackLimit(int slot, @NotNull ItemStack stack) {
+        return Integer.MAX_VALUE;
+    }
+
+    public void clearConfig() {
+        for (var slot : inventory) {
+            slot.setConfig(null);
+            slot.setStock(null);
+        }
+    }
+
+    public boolean hasStackInConfig(ItemStack stack, boolean checkExternal) {
+        if (stack == null || stack.isEmpty()) return false;
+        for (ExportOnlyAEItemSlot slot : inventory) {
+            IAEItemStack config = slot.getConfig();
+            if (config != null && config.isSameType(stack)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isAutoPull() {
+        return false;
+    }
+
+    public boolean isStocking() {
+        return false;
+    }
+}
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEItemSlot.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEItemSlot.java
new file mode 100644
index 00000000000..1408d77ad49
--- /dev/null
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAEItemSlot.java
@@ -0,0 +1,138 @@
+package gregtech.common.metatileentities.multi.multiblockpart.appeng.slot;
+
+import gregtech.common.metatileentities.multi.multiblockpart.appeng.stack.WrappedItemStack;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraftforge.items.IItemHandlerModifiable;
+
+import appeng.api.storage.data.IAEItemStack;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.Consumer;
+
+public class ExportOnlyAEItemSlot extends ExportOnlyAESlot implements IItemHandlerModifiable {
+
+    protected Consumer trigger;
+
+    public ExportOnlyAEItemSlot(IAEItemStack config, IAEItemStack stock) {
+        super(config, stock);
+    }
+
+    public ExportOnlyAEItemSlot() {
+        super();
+    }
+
+    public void setTrigger(Consumer trigger) {
+        this.trigger = trigger;
+    }
+
+    @Override
+    public void deserializeNBT(NBTTagCompound nbt) {
+        if (nbt.hasKey(CONFIG_TAG)) {
+            this.config = WrappedItemStack.fromNBT(nbt.getCompoundTag(CONFIG_TAG));
+        }
+        if (nbt.hasKey(STOCK_TAG)) {
+            this.stock = WrappedItemStack.fromNBT(nbt.getCompoundTag(STOCK_TAG));
+        }
+    }
+
+    @Override
+    public ExportOnlyAEItemSlot copy() {
+        return new ExportOnlyAEItemSlot(
+                this.config == null ? null : this.config.copy(),
+                this.stock == null ? null : this.stock.copy());
+    }
+
+    @Override
+    public void setStackInSlot(int slot, @NotNull ItemStack stack) {}
+
+    @Override
+    public int getSlots() {
+        return 1;
+    }
+
+    @NotNull
+    @Override
+    public ItemStack getStackInSlot(int slot) {
+        if (slot == 0 && this.stock != null) {
+            return this.stock.getDefinition();
+        }
+        return ItemStack.EMPTY;
+    }
+
+    @NotNull
+    @Override
+    public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) {
+        return stack;
+    }
+
+    @NotNull
+    @Override
+    public ItemStack extractItem(int slot, int amount, boolean simulate) {
+        if (slot == 0 && this.stock != null) {
+            int extracted = (int) Math.min(this.stock.getStackSize(), amount);
+            ItemStack result = this.stock.createItemStack();
+            result.setCount(extracted);
+            if (!simulate) {
+                this.stock.decStackSize(extracted);
+                if (this.stock.getStackSize() == 0) {
+                    this.stock = null;
+                }
+            }
+            if (this.trigger != null) {
+                this.trigger.accept(0);
+            }
+            return result;
+        }
+        return ItemStack.EMPTY;
+    }
+
+    @Override
+    public IAEItemStack requestStack() {
+        IAEItemStack result = super.requestStack();
+        if (result instanceof WrappedItemStack) {
+            return ((WrappedItemStack) result).getAEStack();
+        } else {
+            return result;
+        }
+    }
+
+    @Override
+    public IAEItemStack exceedStack() {
+        IAEItemStack result = super.exceedStack();
+        if (result instanceof WrappedItemStack) {
+            return ((WrappedItemStack) result).getAEStack();
+        } else {
+            return result;
+        }
+    }
+
+    @Override
+    public void addStack(IAEItemStack stack) {
+        if (this.stock == null) {
+            this.stock = WrappedItemStack.fromItemStack(stack.createItemStack());
+        } else {
+            this.stock.add(stack);
+        }
+        this.trigger.accept(0);
+    }
+
+    @Override
+    public void setStack(IAEItemStack stack) {
+        if (this.stock == null && stack == null) {
+            return;
+        } else if (stack == null) {
+            this.stock = null;
+        } else {
+            // todo this could maybe be improved with better comparison check
+            this.stock = WrappedItemStack.fromItemStack(stack.createItemStack());
+        }
+        this.trigger.accept(0);
+    }
+
+    @Override
+    public int getSlotLimit(int slot) {
+        return Integer.MAX_VALUE;
+    }
+}
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/ExportOnlyAESlot.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAESlot.java
similarity index 94%
rename from src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/ExportOnlyAESlot.java
rename to src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAESlot.java
index 8d5027add51..316a76e422b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/ExportOnlyAESlot.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/ExportOnlyAESlot.java
@@ -1,4 +1,4 @@
-package gregtech.common.metatileentities.multi.multiblockpart.appeng;
+package gregtech.common.metatileentities.multi.multiblockpart.appeng.slot;
 
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraftforge.common.util.INBTSerializable;
@@ -6,11 +6,6 @@
 import appeng.api.storage.data.IAEStack;
 import org.jetbrains.annotations.Nullable;
 
-/**
- * @author GlodBlock
- * @Description A export only slot to hold {@link IAEStack}
- * @date 2023/4/22-13:42
- */
 public abstract class ExportOnlyAESlot>
                                       implements IConfigurableSlot, INBTSerializable {
 
@@ -64,7 +59,9 @@ public T exceedStack() {
         return null;
     }
 
-    abstract void addStack(T stack);
+    public abstract void addStack(T stack);
+
+    public abstract void setStack(T stack);
 
     @Override
     public NBTTagCompound serializeNBT() {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/IConfigurableSlot.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/IConfigurableSlot.java
similarity index 66%
rename from src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/IConfigurableSlot.java
rename to src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/IConfigurableSlot.java
index 6cc3a48771d..9e4532c8f19 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/IConfigurableSlot.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/slot/IConfigurableSlot.java
@@ -1,10 +1,5 @@
-package gregtech.common.metatileentities.multi.multiblockpart.appeng;
+package gregtech.common.metatileentities.multi.multiblockpart.appeng.slot;
 
-/**
- * @Author GlodBlock
- * @Description A slot that can be set to keep requesting.
- * @Date 2023/4/21-0:34
- */
 public interface IConfigurableSlot {
 
     T getConfig();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/stack/WrappedItemStack.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/stack/WrappedItemStack.java
index 854996b0db4..274d4aac26c 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/stack/WrappedItemStack.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/stack/WrappedItemStack.java
@@ -191,12 +191,18 @@ public boolean sameOre(IAEItemStack iaeItemStack) {
 
     @Override
     public boolean isSameType(IAEItemStack iaeItemStack) {
-        return false;
+        if (iaeItemStack == null) return false;
+        IAEItemStack aeStack = AEItemStack.fromItemStack(this.delegate);
+        if (aeStack == null) return false;
+        return aeStack.isSameType(iaeItemStack);
     }
 
     @Override
     public boolean isSameType(ItemStack itemStack) {
-        return false;
+        if (this.delegate.isEmpty()) return itemStack.isEmpty();
+        IAEItemStack aeStack = AEItemStack.fromItemStack(this.delegate);
+        if (aeStack == null) return false;
+        return aeStack.isSameType(itemStack);
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 56a96fb43ca..1439a5c5554 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -13,6 +13,7 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.*;
+import gregtech.api.util.Mods;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.client.utils.TooltipHelper;
@@ -449,7 +450,7 @@ public static void addWallBlock(@NotNull Block block) {
     }
 
     @ZenMethod("addWallBlock")
-    @Optional.Method(modid = GTValues.MODID_CT)
+    @Optional.Method(modid = Mods.Names.CRAFT_TWEAKER)
     @SuppressWarnings("unused")
     public static void addWallBlockCT(@NotNull IBlock block) {
         WALL_BLOCKS.add(CraftTweakerMC.getBlock(block));
diff --git a/src/main/java/gregtech/common/metatileentities/steam/multiblockpart/MetaTileEntitySteamItemBus.java b/src/main/java/gregtech/common/metatileentities/steam/multiblockpart/MetaTileEntitySteamItemBus.java
index acc41bc21c1..97f855406e2 100644
--- a/src/main/java/gregtech/common/metatileentities/steam/multiblockpart/MetaTileEntitySteamItemBus.java
+++ b/src/main/java/gregtech/common/metatileentities/steam/multiblockpart/MetaTileEntitySteamItemBus.java
@@ -24,7 +24,7 @@
 import codechicken.lib.vec.Matrix4;
 import com.cleanroommc.modularui.api.drawable.IKey;
 import com.cleanroommc.modularui.api.widget.IWidget;
-import com.cleanroommc.modularui.manager.GuiCreationContext;
+import com.cleanroommc.modularui.factory.PosGuiData;
 import com.cleanroommc.modularui.screen.ModularPanel;
 import com.cleanroommc.modularui.value.sync.GuiSyncManager;
 import com.cleanroommc.modularui.value.sync.SyncHandlers;
@@ -86,8 +86,7 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
     }
 
     @Override
-    public ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManager guiSyncManager,
-                                boolean isClient) {
+    public ModularPanel buildUI(PosGuiData guiData, GuiSyncManager guiSyncManager) {
         guiSyncManager.registerSlotGroup("item_inv", 2);
 
         List> widgets = new ArrayList<>();
diff --git a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java
index a3680aadfd1..44859246953 100644
--- a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java
+++ b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityCrate.java
@@ -33,7 +33,7 @@
 import codechicken.lib.vec.Matrix4;
 import com.cleanroommc.modularui.api.drawable.IKey;
 import com.cleanroommc.modularui.api.widget.IWidget;
-import com.cleanroommc.modularui.manager.GuiCreationContext;
+import com.cleanroommc.modularui.factory.PosGuiData;
 import com.cleanroommc.modularui.screen.ModularPanel;
 import com.cleanroommc.modularui.value.sync.GuiSyncManager;
 import com.cleanroommc.modularui.value.sync.SyncHandlers;
@@ -146,8 +146,7 @@ public boolean usesMui2() {
     }
 
     @Override
-    public ModularPanel buildUI(GuiCreationContext guiCreationContext, GuiSyncManager guiSyncManager,
-                                boolean isClient) {
+    public ModularPanel buildUI(PosGuiData guiData, GuiSyncManager guiSyncManager) {
         guiSyncManager.registerSlotGroup("item_inv", rowSize);
 
         int rows = inventorySize / rowSize;
diff --git a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityDrum.java b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityDrum.java
index ae83ad54a49..321012fcf8f 100644
--- a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityDrum.java
+++ b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityDrum.java
@@ -44,6 +44,7 @@
 import codechicken.lib.vec.Matrix4;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.IOException;
@@ -97,7 +98,7 @@ protected void initializeInventory() {
         IPropertyFluidFilter filter = this.material.getProperty(PropertyKey.FLUID_PIPE);
         if (filter == null) {
             throw new IllegalArgumentException(
-                    String.format("Material %s requires FluidPipePropety for Drums", material));
+                    String.format("Material %s requires FluidPipeProperty for Drums", material));
         }
         this.fluidInventory = this.fluidTank = new FilteredFluidHandler(tankSize).setFilter(filter);
     }
@@ -129,7 +130,7 @@ public ICapabilityProvider initItemStackCapabilities(ItemStack itemStack) {
     }
 
     @Override
-    public void writeInitialSyncData(PacketBuffer buf) {
+    public void writeInitialSyncData(@NotNull PacketBuffer buf) {
         super.writeInitialSyncData(buf);
         FluidStack fluidStack = fluidTank.getFluid();
         buf.writeBoolean(fluidStack != null);
@@ -142,7 +143,7 @@ public void writeInitialSyncData(PacketBuffer buf) {
     }
 
     @Override
-    public void receiveInitialSyncData(PacketBuffer buf) {
+    public void receiveInitialSyncData(@NotNull PacketBuffer buf) {
         super.receiveInitialSyncData(buf);
         FluidStack fluidStack = null;
         if (buf.readBoolean()) {
@@ -156,7 +157,7 @@ public void receiveInitialSyncData(PacketBuffer buf) {
     }
 
     @Override
-    public void receiveCustomData(int dataId, PacketBuffer buf) {
+    public void receiveCustomData(int dataId, @NotNull PacketBuffer buf) {
         super.receiveCustomData(dataId, buf);
         if (dataId == UPDATE_AUTO_OUTPUT) {
             this.isAutoOutput = buf.readBoolean();
@@ -266,7 +267,7 @@ public void addInformation(ItemStack stack, @Nullable World player, List
             FluidStack fluidStack = FluidStack.loadFluidStackFromNBT(tagCompound.getCompoundTag("Fluid"));
             if (fluidStack == null) return;
             tooltip.add(I18n.format("gregtech.machine.fluid_tank.fluid", fluidStack.amount,
-                    I18n.format(fluidStack.getUnlocalizedName())));
+                    fluidStack.getFluid().getLocalizedName(fluidStack)));
         }
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumTank.java b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumTank.java
index 6e27064a4e3..d6624d95823 100644
--- a/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumTank.java
+++ b/src/main/java/gregtech/common/metatileentities/storage/MetaTileEntityQuantumTank.java
@@ -11,7 +11,14 @@
 import gregtech.api.cover.CoverRayTracer;
 import gregtech.api.gui.GuiTextures;
 import gregtech.api.gui.ModularUI;
-import gregtech.api.gui.widgets.*;
+import gregtech.api.gui.widgets.AdvancedTextWidget;
+import gregtech.api.gui.widgets.FluidContainerSlotWidget;
+import gregtech.api.gui.widgets.ImageWidget;
+import gregtech.api.gui.widgets.LabelWidget;
+import gregtech.api.gui.widgets.PhantomTankWidget;
+import gregtech.api.gui.widgets.SlotWidget;
+import gregtech.api.gui.widgets.TankWidget;
+import gregtech.api.gui.widgets.ToggleButtonWidget;
 import gregtech.api.items.itemhandlers.GTItemStackHandler;
 import gregtech.api.metatileentity.IFastRenderMetaTileEntity;
 import gregtech.api.metatileentity.ITieredMetaTileEntity;
@@ -443,14 +450,16 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             try {
                 this.fluidTank.setFluid(FluidStack.loadFluidStackFromNBT(buf.readCompoundTag()));
             } catch (IOException ignored) {
-                GTLog.logger.warn("Failed to load fluid from NBT in a quantum tank at " + this.getPos() +
-                        " on a routine fluid update");
+                GTLog.logger.warn("Failed to load fluid from NBT in a quantum tank at {} on a routine fluid update",
+                        this.getPos());
             }
             scheduleRenderUpdate();
         } else if (dataId == UPDATE_FLUID_AMOUNT) {
+            // amount must always be read even if it cannot be used to ensure the reader index advances
+            int amount = buf.readInt();
             FluidStack stack = fluidTank.getFluid();
             if (stack != null) {
-                stack.amount = Math.min(buf.readInt(), fluidTank.getCapacity());
+                stack.amount = Math.min(amount, fluidTank.getCapacity());
                 scheduleRenderUpdate();
             }
         } else if (dataId == UPDATE_IS_VOIDING) {
diff --git a/src/main/java/gregtech/common/pipelike/cable/BlockCable.java b/src/main/java/gregtech/common/pipelike/cable/BlockCable.java
index d384e22e608..204010ed522 100644
--- a/src/main/java/gregtech/common/pipelike/cable/BlockCable.java
+++ b/src/main/java/gregtech/common/pipelike/cable/BlockCable.java
@@ -1,6 +1,5 @@
 package gregtech.common.pipelike.cable;
 
-import gregtech.api.GregTechAPI;
 import gregtech.api.capability.GregtechCapabilities;
 import gregtech.api.damagesources.DamageSources;
 import gregtech.api.items.toolitem.ToolClasses;
@@ -14,6 +13,7 @@
 import gregtech.api.util.GTUtility;
 import gregtech.client.renderer.pipe.CableRenderer;
 import gregtech.client.renderer.pipe.PipeRenderer;
+import gregtech.common.creativetab.GTCreativeTabs;
 import gregtech.common.pipelike.cable.net.WorldENet;
 import gregtech.common.pipelike.cable.tile.TileEntityCable;
 import gregtech.common.pipelike.cable.tile.TileEntityCableTickable;
@@ -54,7 +54,7 @@ public class BlockCable extends BlockMaterialPipe {
 
@@ -67,6 +70,11 @@ public void onPipeConnectionsUpdate() {
         NET_DATA.clear();
     }
 
+    @Override
+    public void onChunkUnload() {
+        NET_DATA.clear();
+    }
+
     @Override
     protected void transferNodeData(Map> transferredNodes,
                                     PipeNet parentNet) {
diff --git a/src/main/java/gregtech/common/pipelike/cable/tile/TileEntityCable.java b/src/main/java/gregtech/common/pipelike/cable/tile/TileEntityCable.java
index d54d160e525..befdf615466 100644
--- a/src/main/java/gregtech/common/pipelike/cable/tile/TileEntityCable.java
+++ b/src/main/java/gregtech/common/pipelike/cable/tile/TileEntityCable.java
@@ -258,7 +258,7 @@ public  T getCapabilityInternal(Capability capability, @Nullable EnumFacin
         if (capability == GregtechCapabilities.CAPABILITY_ENERGY_CONTAINER) {
             if (world.isRemote)
                 return GregtechCapabilities.CAPABILITY_ENERGY_CONTAINER.cast(clientCapability);
-            if (handlers.size() == 0)
+            if (handlers.isEmpty())
                 initHandlers();
             checkNetwork();
             return GregtechCapabilities.CAPABILITY_ENERGY_CONTAINER.cast(handlers.getOrDefault(facing, defaultHandler));
@@ -293,6 +293,12 @@ private EnergyNet getEnergyNet() {
         return currentEnergyNet;
     }
 
+    @Override
+    public void onChunkUnload() {
+        super.onChunkUnload();
+        this.handlers.clear();
+    }
+
     @Override
     public int getDefaultPaintingColor() {
         return 0x404040;
diff --git a/src/main/java/gregtech/common/pipelike/fluidpipe/BlockFluidPipe.java b/src/main/java/gregtech/common/pipelike/fluidpipe/BlockFluidPipe.java
index f4e4975fa6c..20ac0640a0f 100644
--- a/src/main/java/gregtech/common/pipelike/fluidpipe/BlockFluidPipe.java
+++ b/src/main/java/gregtech/common/pipelike/fluidpipe/BlockFluidPipe.java
@@ -1,6 +1,5 @@
 package gregtech.common.pipelike.fluidpipe;
 
-import gregtech.api.GregTechAPI;
 import gregtech.api.items.toolitem.ToolClasses;
 import gregtech.api.pipenet.block.material.BlockMaterialPipe;
 import gregtech.api.pipenet.tile.IPipeTile;
@@ -11,6 +10,7 @@
 import gregtech.api.util.EntityDamageUtil;
 import gregtech.client.renderer.pipe.FluidPipeRenderer;
 import gregtech.client.renderer.pipe.PipeRenderer;
+import gregtech.common.creativetab.GTCreativeTabs;
 import gregtech.common.pipelike.fluidpipe.net.WorldFluidPipeNet;
 import gregtech.common.pipelike.fluidpipe.tile.TileEntityFluidPipe;
 import gregtech.common.pipelike.fluidpipe.tile.TileEntityFluidPipeTickable;
@@ -48,7 +48,7 @@ public class BlockFluidPipe extends BlockMaterialPipe {
 
@@ -43,6 +47,11 @@ public void onPipeConnectionsUpdate() {
         NET_DATA.clear();
     }
 
+    @Override
+    public void onChunkUnload() {
+        NET_DATA.clear();
+    }
+
     @Override
     protected void transferNodeData(Map> transferredNodes,
                                     PipeNet parentNet) {
diff --git a/src/main/java/gregtech/common/pipelike/itempipe/tile/TileEntityItemPipe.java b/src/main/java/gregtech/common/pipelike/itempipe/tile/TileEntityItemPipe.java
index 5880f375964..80b5e233b92 100644
--- a/src/main/java/gregtech/common/pipelike/itempipe/tile/TileEntityItemPipe.java
+++ b/src/main/java/gregtech/common/pipelike/itempipe/tile/TileEntityItemPipe.java
@@ -153,4 +153,10 @@ public int getTransferredItems() {
         updateTransferredState();
         return this.transferredItems;
     }
+
+    @Override
+    public void onChunkUnload() {
+        super.onChunkUnload();
+        this.handlers.clear();
+    }
 }
diff --git a/src/main/java/gregtech/common/pipelike/laser/BlockLaserPipe.java b/src/main/java/gregtech/common/pipelike/laser/BlockLaserPipe.java
index c7adec7b706..ff6f9c693f8 100644
--- a/src/main/java/gregtech/common/pipelike/laser/BlockLaserPipe.java
+++ b/src/main/java/gregtech/common/pipelike/laser/BlockLaserPipe.java
@@ -1,6 +1,5 @@
 package gregtech.common.pipelike.laser;
 
-import gregtech.api.GregTechAPI;
 import gregtech.api.capability.GregtechTileCapabilities;
 import gregtech.api.items.toolitem.ToolClasses;
 import gregtech.api.items.toolitem.ToolHelper;
@@ -9,6 +8,7 @@
 import gregtech.api.pipenet.tile.TileEntityPipeBase;
 import gregtech.client.renderer.pipe.LaserPipeRenderer;
 import gregtech.client.utils.BloomEffectUtil;
+import gregtech.common.creativetab.GTCreativeTabs;
 import gregtech.common.pipelike.laser.net.WorldLaserPipeNet;
 import gregtech.common.pipelike.laser.tile.TileEntityLaserPipe;
 
@@ -39,7 +39,7 @@ public class BlockLaserPipe extends BlockPipe> transferredNodes,
                                     PipeNet parentNet) {
diff --git a/src/main/java/gregtech/common/pipelike/laser/tile/TileEntityLaserPipe.java b/src/main/java/gregtech/common/pipelike/laser/tile/TileEntityLaserPipe.java
index 6a11c0503a4..759fa494152 100644
--- a/src/main/java/gregtech/common/pipelike/laser/tile/TileEntityLaserPipe.java
+++ b/src/main/java/gregtech/common/pipelike/laser/tile/TileEntityLaserPipe.java
@@ -224,6 +224,12 @@ public void readFromNBT(@NotNull NBTTagCompound compound) {
         }
     }
 
+    @Override
+    public void onChunkUnload() {
+        super.onChunkUnload();
+        this.handlers.clear();
+    }
+
     private static class DefaultLaserContainer implements ILaserContainer {
 
         @Override
diff --git a/src/main/java/gregtech/common/pipelike/optical/BlockOpticalPipe.java b/src/main/java/gregtech/common/pipelike/optical/BlockOpticalPipe.java
index 87426fb81de..3e52f60faba 100644
--- a/src/main/java/gregtech/common/pipelike/optical/BlockOpticalPipe.java
+++ b/src/main/java/gregtech/common/pipelike/optical/BlockOpticalPipe.java
@@ -1,6 +1,5 @@
 package gregtech.common.pipelike.optical;
 
-import gregtech.api.GregTechAPI;
 import gregtech.api.capability.GregtechTileCapabilities;
 import gregtech.api.items.toolitem.ToolClasses;
 import gregtech.api.items.toolitem.ToolHelper;
@@ -8,6 +7,7 @@
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.pipenet.tile.TileEntityPipeBase;
 import gregtech.client.renderer.pipe.OpticalPipeRenderer;
+import gregtech.common.creativetab.GTCreativeTabs;
 import gregtech.common.pipelike.optical.net.WorldOpticalPipeNet;
 import gregtech.common.pipelike.optical.tile.TileEntityOpticalPipe;
 
@@ -37,7 +37,7 @@ public class BlockOpticalPipe extends BlockPipe> transferredNodes,
                                     PipeNet parentNet) {
diff --git a/src/main/java/gregtech/common/pipelike/optical/tile/TileEntityOpticalPipe.java b/src/main/java/gregtech/common/pipelike/optical/tile/TileEntityOpticalPipe.java
index adf7f995ae1..43efe04cb56 100644
--- a/src/main/java/gregtech/common/pipelike/optical/tile/TileEntityOpticalPipe.java
+++ b/src/main/java/gregtech/common/pipelike/optical/tile/TileEntityOpticalPipe.java
@@ -194,6 +194,12 @@ public void receiveCustomData(int discriminator, PacketBuffer buf) {
         }
     }
 
+    @Override
+    public void onChunkUnload() {
+        super.onChunkUnload();
+        this.handlers.clear();
+    }
+
     private static class DefaultDataHandler implements IDataAccessHatch {
 
         @Override
diff --git a/src/main/java/gregtech/common/terminal/app/prospector/widget/WidgetProspectingMap.java b/src/main/java/gregtech/common/terminal/app/prospector/widget/WidgetProspectingMap.java
index 106b142c66f..ba42b1a29cc 100644
--- a/src/main/java/gregtech/common/terminal/app/prospector/widget/WidgetProspectingMap.java
+++ b/src/main/java/gregtech/common/terminal/app/prospector/widget/WidgetProspectingMap.java
@@ -1,6 +1,5 @@
 package gregtech.common.terminal.app.prospector.widget;
 
-import gregtech.api.GTValues;
 import gregtech.api.gui.IRenderContext;
 import gregtech.api.gui.Widget;
 import gregtech.api.unification.OreDictUnifier;
@@ -33,7 +32,6 @@
 import net.minecraftforge.fluids.FluidRegistry;
 import net.minecraftforge.fluids.FluidStack;
 import net.minecraftforge.fml.common.FMLCommonHandler;
-import net.minecraftforge.fml.common.Loader;
 import net.minecraftforge.fml.common.Optional;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
@@ -308,8 +306,7 @@ public void drawInForeground(int mouseX, int mouseY) {
                 }
             }
 
-            if (Loader.isModLoaded(GTValues.MODID_JOURNEYMAP) || Loader.isModLoaded(GTValues.MODID_VOXELMAP) ||
-                    Loader.isModLoaded(GTValues.MODID_XAERO_MINIMAP)) {
+            if (Mods.JourneyMap.isModLoaded() || Mods.VoxelMap.isModLoaded() || Mods.XaerosMinimap.isModLoaded()) {
                 tooltips.add(I18n.format("terminal.prospector.waypoint.add"));
             }
             this.drawHoveringText(ItemStack.EMPTY, tooltips, 300, mouseX, mouseY);
@@ -336,11 +333,11 @@ public boolean mouseClicked(int mouseX, int mouseY, int button) {
             boolean added = false;
             trimHoveredNames();
 
-            if (Loader.isModLoaded(GTValues.MODID_JOURNEYMAP)) {
+            if (Mods.JourneyMap.isModLoaded()) {
                 added = addJourneymapWaypoint(b);
-            } else if (Loader.isModLoaded(GTValues.MODID_VOXELMAP)) {
+            } else if (Mods.VoxelMap.isModLoaded()) {
                 added = addVoxelMapWaypoint(b);
-            } else if (Loader.isModLoaded(GTValues.MODID_XAERO_MINIMAP)) {
+            } else if (Mods.XaerosMinimap.isModLoaded()) {
                 added = addXaeroMapWaypoint(b);
             }
             if (added) {
@@ -382,7 +379,7 @@ private String createVeinName() {
         return s.substring(1, s.length() - 1);
     }
 
-    @Optional.Method(modid = GTValues.MODID_JOURNEYMAP)
+    @Optional.Method(modid = Mods.Names.JOURNEY_MAP)
     private boolean addJourneymapWaypoint(BlockPos b) {
         journeymap.client.model.Waypoint journeyMapWaypoint = new journeymap.client.model.Waypoint(createVeinName(),
                 b,
@@ -396,7 +393,7 @@ private boolean addJourneymapWaypoint(BlockPos b) {
         return false;
     }
 
-    @Optional.Method(modid = GTValues.MODID_VOXELMAP)
+    @Optional.Method(modid = Mods.Names.VOXEL_MAP)
     private boolean addVoxelMapWaypoint(@NotNull BlockPos b) {
         Color c = new Color(color);
         TreeSet world = new TreeSet<>();
@@ -425,7 +422,7 @@ private boolean addVoxelMapWaypoint(@NotNull BlockPos b) {
         return false;
     }
 
-    @Optional.Method(modid = GTValues.MODID_XAERO_MINIMAP)
+    @Optional.Method(modid = Mods.Names.XAEROS_MINIMAP)
     private boolean addXaeroMapWaypoint(@NotNull BlockPos b) {
         int red = clampColor(color >> 16 & 0xFF);
         int green = clampColor(color >> 8 & 0xFF);
diff --git a/src/main/java/gregtech/common/terminal/app/worldprospector/WorldProspectorARApp.java b/src/main/java/gregtech/common/terminal/app/worldprospector/WorldProspectorARApp.java
index f4b7fe6f2e1..8ead4466af8 100644
--- a/src/main/java/gregtech/common/terminal/app/worldprospector/WorldProspectorARApp.java
+++ b/src/main/java/gregtech/common/terminal/app/worldprospector/WorldProspectorARApp.java
@@ -17,6 +17,7 @@
 import gregtech.api.unification.OreDictUnifier;
 import gregtech.api.unification.stack.MaterialStack;
 import gregtech.api.util.GTLog;
+import gregtech.api.util.Mods;
 import gregtech.client.shader.Shaders;
 import gregtech.client.utils.DepthTextureUtil;
 import gregtech.client.utils.RenderBufferHelper;
@@ -490,7 +491,7 @@ private static void renderScan(float getPartialTicks) {
         Minecraft mc = Minecraft.getMinecraft();
         World world = mc.world;
         Entity viewer = mc.getRenderViewEntity();
-        if (world != null && viewer != null && !Shaders.isOptiFineShaderPackLoaded()) {
+        if (world != null && viewer != null && !Mods.Optifine.isModLoaded()) {
 
             Framebuffer fbo = mc.getFramebuffer();
 
diff --git a/src/main/java/gregtech/core/CoreModule.java b/src/main/java/gregtech/core/CoreModule.java
index 56c1196049e..46eeb3e8335 100644
--- a/src/main/java/gregtech/core/CoreModule.java
+++ b/src/main/java/gregtech/core/CoreModule.java
@@ -15,6 +15,7 @@
 import gregtech.api.modules.IGregTechModule;
 import gregtech.api.mui.GTGuiTextures;
 import gregtech.api.mui.GTGuiTheme;
+import gregtech.api.mui.GTGuis;
 import gregtech.api.recipes.ModHandler;
 import gregtech.api.recipes.RecipeMap;
 import gregtech.api.recipes.recipeproperties.TemperatureProperty;
@@ -27,6 +28,7 @@
 import gregtech.api.util.CapesRegistry;
 import gregtech.api.util.VirtualTankRegistry;
 import gregtech.api.util.input.KeyBind;
+import gregtech.api.util.oreglob.OreGlob;
 import gregtech.api.worldgen.bedrockFluids.BedrockFluidVeinHandler;
 import gregtech.api.worldgen.bedrockFluids.BedrockFluidVeinSaveData;
 import gregtech.api.worldgen.config.WorldGenRegistry;
@@ -42,6 +44,7 @@
 import gregtech.common.command.worldgen.CommandWorldgen;
 import gregtech.common.covers.CoverBehaviors;
 import gregtech.common.covers.filter.FilterTypeRegistry;
+import gregtech.common.covers.filter.oreglob.impl.OreGlobParser;
 import gregtech.common.items.MetaItems;
 import gregtech.common.items.ToolItems;
 import gregtech.common.metatileentities.MetaTileEntities;
@@ -113,6 +116,8 @@ public CoreModule() {
         // must be set here because of GroovyScript compat
         // trying to read this before the pre-init stage
         GregTechAPI.materialManager = MaterialRegistryManager.getInstance();
+
+        OreGlob.setCompiler((expr, ignoreCase) -> new OreGlobParser(expr, ignoreCase).compile());
     }
 
     @NotNull
@@ -131,6 +136,7 @@ public void preInit(FMLPreInitializationEvent event) {
         GTSoundEvents.register();
 
         /* MUI Initialization */
+        GTGuis.registerFactories();
         GTGuiTextures.init();
         GTGuiTheme.registerThemes();
 
diff --git a/src/main/java/gregtech/core/network/internal/NetworkHandler.java b/src/main/java/gregtech/core/network/internal/NetworkHandler.java
index 60849c35415..3d6769891f9 100644
--- a/src/main/java/gregtech/core/network/internal/NetworkHandler.java
+++ b/src/main/java/gregtech/core/network/internal/NetworkHandler.java
@@ -7,6 +7,7 @@
 import gregtech.api.network.INetworkHandler;
 import gregtech.api.network.IPacket;
 import gregtech.api.network.IServerExecutor;
+import gregtech.api.util.GTLog;
 import gregtech.core.CoreModule;
 
 import net.minecraft.client.network.NetHandlerPlayClient;
@@ -26,8 +27,11 @@
 import net.minecraftforge.fml.relauncher.SideOnly;
 
 import io.netty.buffer.Unpooled;
+import org.jetbrains.annotations.NotNull;
 
-public class NetworkHandler implements INetworkHandler {
+import java.lang.reflect.InvocationTargetException;
+
+public final class NetworkHandler implements INetworkHandler {
 
     private static final NetworkHandler INSTANCE = new NetworkHandler();
 
@@ -106,7 +110,7 @@ public void sendToServer(IPacket packet) {
 
     @SubscribeEvent
     @SideOnly(Side.CLIENT)
-    public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent event) throws Exception {
+    public void onClientPacket(FMLNetworkEvent.@NotNull ClientCustomPacketEvent event) throws Exception {
         IPacket packet = toGTPacket(event.getPacket());
         if (IClientExecutor.class.isAssignableFrom(packet.getClass())) {
             IClientExecutor executor = (IClientExecutor) packet;
@@ -121,7 +125,7 @@ public void onClientPacket(FMLNetworkEvent.ClientCustomPacketEvent event) throws
     }
 
     @SubscribeEvent
-    public void onServerPacket(FMLNetworkEvent.ServerCustomPacketEvent event) throws Exception {
+    public void onServerPacket(FMLNetworkEvent.@NotNull ServerCustomPacketEvent event) throws Exception {
         IPacket packet = toGTPacket(event.getPacket());
         if (IServerExecutor.class.isAssignableFrom(packet.getClass())) {
             IServerExecutor executor = (IServerExecutor) packet;
@@ -135,17 +139,27 @@ public void onServerPacket(FMLNetworkEvent.ServerCustomPacketEvent event) throws
         }
     }
 
-    private FMLProxyPacket toFMLPacket(IPacket packet) {
+    private @NotNull FMLProxyPacket toFMLPacket(@NotNull IPacket packet) {
         PacketBuffer buf = new PacketBuffer(Unpooled.buffer());
         buf.writeVarInt(packetHandler.getPacketId(packet.getClass()));
         packet.encode(buf);
         return new FMLProxyPacket(buf, GTValues.MODID);
     }
 
-    private IPacket toGTPacket(FMLProxyPacket proxyPacket) throws Exception {
+    private @NotNull IPacket toGTPacket(@NotNull FMLProxyPacket proxyPacket) throws NoSuchMethodException,
+                                                                             InvocationTargetException,
+                                                                             InstantiationException,
+                                                                             IllegalAccessException {
         PacketBuffer payload = (PacketBuffer) proxyPacket.payload();
-        IPacket packet = packetHandler.getPacketClass(payload.readVarInt()).newInstance();
+        var clazz = packetHandler.getPacketClass(payload.readVarInt());
+        IPacket packet = clazz.getConstructor().newInstance();
         packet.decode(payload);
+
+        if (payload.readableBytes() != 0) {
+            GTLog.logger.error(
+                    "NetworkHandler failed to finish reading packet with class {} and {} bytes remaining",
+                    clazz.getName(), payload.readableBytes());
+        }
         return packet;
     }
 }
diff --git a/src/main/java/gregtech/integration/IntegrationModule.java b/src/main/java/gregtech/integration/IntegrationModule.java
index 15d760b4a63..65f02e3c938 100644
--- a/src/main/java/gregtech/integration/IntegrationModule.java
+++ b/src/main/java/gregtech/integration/IntegrationModule.java
@@ -2,12 +2,12 @@
 
 import gregtech.api.GTValues;
 import gregtech.api.modules.GregTechModule;
+import gregtech.api.util.Mods;
 import gregtech.common.items.MetaItems;
 import gregtech.modules.BaseGregTechModule;
 import gregtech.modules.GregTechModules;
 
 import net.minecraftforge.event.RegistryEvent;
-import net.minecraftforge.fml.common.Loader;
 import net.minecraftforge.fml.common.Optional;
 import net.minecraftforge.fml.common.event.FMLInitializationEvent;
 import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@@ -47,13 +47,13 @@ public List> getEventBusSubscribers() {
     public void init(FMLInitializationEvent event) {
         super.init(event);
 
-        if (Loader.isModLoaded(GTValues.MODID_IE)) {
+        if (Mods.ImmersiveEngineering.isModLoaded()) {
             BelljarHandler.registerBasicItemFertilizer(MetaItems.FERTILIZER.getStackForm(), 1.25f);
             logger.info("Registered Immersive Engineering Compat");
         }
     }
 
-    @Optional.Method(modid = GTValues.MODID_EIO)
+    @Optional.Method(modid = Mods.Names.ENDER_IO)
     @SubscribeEvent
     public static void registerFertilizer(@NotNull RegistryEvent.Register event) {
         event.getRegistry().register(new Bonemeal(MetaItems.FERTILIZER.getStackForm()));
diff --git a/src/main/java/gregtech/integration/IntegrationUtil.java b/src/main/java/gregtech/integration/IntegrationUtil.java
index 88cfe6ec990..c25edab7be0 100644
--- a/src/main/java/gregtech/integration/IntegrationUtil.java
+++ b/src/main/java/gregtech/integration/IntegrationUtil.java
@@ -11,6 +11,7 @@
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -18,9 +19,16 @@
 import java.util.Arrays;
 import java.util.List;
 
+@Deprecated
 public class IntegrationUtil {
 
-    /** Should only be called after {@link net.minecraftforge.fml.common.event.FMLPreInitializationEvent} */
+    /**
+     * Should only be called after {@link net.minecraftforge.fml.common.event.FMLPreInitializationEvent}
+     *
+     * @deprecated Use {@link gregtech.api.util.Mods} instead for these features.
+     */
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
     public static void throwIncompatibilityIfLoaded(String modID, String... customMessages) {
         if (Loader.isModLoaded(modID)) {
             String modName = TextFormatting.BOLD + modID + TextFormatting.RESET;
@@ -31,7 +39,13 @@ public static void throwIncompatibilityIfLoaded(String modID, String... customMe
         }
     }
 
-    /** Should only be called after {@link net.minecraftforge.fml.common.event.FMLPreInitializationEvent} */
+    /**
+     * Should only be called after {@link net.minecraftforge.fml.common.event.FMLPreInitializationEvent}
+     *
+     * @deprecated Use {@link gregtech.api.util.Mods} instead for these features.
+     */
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
     public static void throwIncompatibility(List messages) {
         if (FMLLaunchHandler.side() == Side.SERVER) {
             throw new RuntimeException(String.join(",", messages));
@@ -40,16 +54,31 @@ public static void throwIncompatibility(List messages) {
         }
     }
 
+    /**
+     * @deprecated Use {@link gregtech.api.util.Mods} instead for these features.
+     */
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
     @NotNull
     public static ItemStack getModItem(@NotNull String modid, @NotNull String name, int meta) {
         return getModItem(modid, name, meta, 1, null);
     }
 
+    /**
+     * @deprecated Use {@link gregtech.api.util.Mods} instead for these features.
+     */
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
     @NotNull
     public static ItemStack getModItem(@NotNull String modid, @NotNull String name, int meta, int amount) {
         return getModItem(modid, name, meta, amount, null);
     }
 
+    /**
+     * @deprecated Use {@link gregtech.api.util.Mods} instead for these features.
+     */
+    @Deprecated
+    @ApiStatus.ScheduledForRemoval(inVersion = "2.9")
     @NotNull
     public static ItemStack getModItem(@NotNull String modid, @NotNull String name, int meta, int amount,
                                        @Nullable String nbt) {
diff --git a/src/main/java/gregtech/integration/baubles/BaublesModule.java b/src/main/java/gregtech/integration/baubles/BaublesModule.java
index 4371d5a032d..6f725211e8f 100644
--- a/src/main/java/gregtech/integration/baubles/BaublesModule.java
+++ b/src/main/java/gregtech/integration/baubles/BaublesModule.java
@@ -2,6 +2,7 @@
 
 import gregtech.api.GTValues;
 import gregtech.api.modules.GregTechModule;
+import gregtech.api.util.Mods;
 import gregtech.common.items.MetaItems;
 import gregtech.integration.IntegrationSubmodule;
 import gregtech.modules.GregTechModules;
@@ -24,7 +25,7 @@
 @GregTechModule(
                 moduleID = GregTechModules.MODULE_BAUBLES,
                 containerID = GTValues.MODID,
-                modDependencies = GTValues.MODID_BAUBLES,
+                modDependencies = Mods.Names.BAUBLES,
                 name = "GregTech Baubles Integration",
                 description = "Baubles Integration Module")
 public class BaublesModule extends IntegrationSubmodule {
diff --git a/src/main/java/gregtech/integration/chisel/ChiselModule.java b/src/main/java/gregtech/integration/chisel/ChiselModule.java
new file mode 100644
index 00000000000..c8a5f8d3416
--- /dev/null
+++ b/src/main/java/gregtech/integration/chisel/ChiselModule.java
@@ -0,0 +1,120 @@
+package gregtech.integration.chisel;
+
+import gregtech.api.GTValues;
+import gregtech.api.block.VariantBlock;
+import gregtech.api.modules.GregTechModule;
+import gregtech.api.unification.material.Material;
+import gregtech.api.unification.material.Materials;
+import gregtech.api.util.Mods;
+import gregtech.common.blocks.BlockColored;
+import gregtech.common.blocks.BlockCompressed;
+import gregtech.common.blocks.BlockWarningSign;
+import gregtech.common.blocks.BlockWarningSign1;
+import gregtech.common.blocks.MetaBlocks;
+import gregtech.common.blocks.StoneVariantBlock;
+import gregtech.common.blocks.StoneVariantBlock.StoneType;
+import gregtech.common.blocks.StoneVariantBlock.StoneVariant;
+import gregtech.common.blocks.wood.BlockGregPlanks;
+import gregtech.integration.IntegrationSubmodule;
+import gregtech.modules.GregTechModules;
+
+import net.minecraft.block.Block;
+import net.minecraft.item.EnumDyeColor;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.IStringSerializable;
+import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLInterModComms;
+
+import team.chisel.common.carving.Carving;
+
+import java.util.Objects;
+
+@GregTechModule(
+                moduleID = GregTechModules.MODULE_CHISEL,
+                containerID = GTValues.MODID,
+                modDependencies = Mods.Names.CHISEL,
+                name = "GregTech Chisel Integration",
+                description = "Chisel Integration Module")
+public class ChiselModule extends IntegrationSubmodule {
+
+    @Override
+    public void init(FMLInitializationEvent event) {
+        // GT custom groups
+        addVariations("gt_warning_sign", MetaBlocks.WARNING_SIGN, BlockWarningSign.SignType.values());
+        addVariations("gt_warning_sign", MetaBlocks.WARNING_SIGN_1, BlockWarningSign1.SignType.values());
+        addVariations("gt_studs", MetaBlocks.STUDS);
+        addVariations("gt_metal_sheet", MetaBlocks.METAL_SHEET);
+        addVariations("gt_large_metal_sheet", MetaBlocks.LARGE_METAL_SHEET);
+        for (EnumDyeColor color : EnumDyeColor.values()) {
+            Block lamp = MetaBlocks.LAMPS.get(color);
+            Block lampBorderless = MetaBlocks.BORDERLESS_LAMPS.get(color);
+            String group = "gt_lamp_" + color.getName().toLowerCase();
+            for (int i = 0; i < 8; i++) {
+                addVariation(group, lamp, i);
+                addVariation(group, lampBorderless, i);
+            }
+        }
+
+        // Chisel shared groups
+        addVariations("marble", StoneType.MARBLE, false);
+        addVariations("basalt", StoneType.BASALT, false);
+        addVariations("black_granite", StoneType.BLACK_GRANITE, false);
+        addVariations("red_granite", StoneType.RED_GRANITE, false);
+        addVariations("light_concrete", StoneType.CONCRETE_LIGHT, true);
+        addVariations("dark_concrete", StoneType.CONCRETE_DARK, true);
+
+        // Mod-dependent groups
+        if (doesGroupExist("treated_wood")) { // IE Treated Wood group
+            addVariations("treated_wood", MetaBlocks.PLANKS, BlockGregPlanks.BlockType.TREATED_PLANK);
+        }
+        if (doesGroupExist("certus")) { // AE2 Certus Quartz group
+            addVariation("certus", Materials.CertusQuartz);
+        }
+    }
+
+    @SafeVarargs
+    private  & IStringSerializable, T extends VariantBlock> void addVariations(String group,
+                                                                                                    T block,
+                                                                                                    U... variants) {
+        if (variants != null) {
+            for (U variant : variants) {
+                addVariation(group, block, block.getMetaFromState(block.getState(variant)));
+            }
+        }
+    }
+
+    private void addVariations(String group, BlockColored block) {
+        for (EnumDyeColor color : EnumDyeColor.values()) {
+            addVariation(group, block, color.getMetadata());
+        }
+    }
+
+    private void addVariations(String group, StoneType type, boolean enableCobbles) {
+        for (StoneVariantBlock.StoneVariant variant : StoneVariant.values()) {
+            if (!enableCobbles && (variant == StoneVariant.COBBLE || variant == StoneVariant.COBBLE_MOSSY)) {
+                continue;
+            }
+            StoneVariantBlock block = MetaBlocks.STONE_BLOCKS.get(variant);
+            int meta = block.getMetaFromState(block.getState(type));
+            addVariation(group, block, meta);
+        }
+    }
+
+    private void addVariation(String group, Material material) {
+        BlockCompressed block = MetaBlocks.COMPRESSED.get(material);
+        int meta = block.getMetaFromState(block.getBlock(material));
+        addVariation(group, block, meta);
+    }
+
+    private void addVariation(String group, Block block, int meta) {
+        NBTTagCompound tag = new NBTTagCompound();
+        tag.setString("group", group);
+        tag.setString("block", Objects.requireNonNull(block.getRegistryName()).toString());
+        tag.setInteger("meta", meta);
+        FMLInterModComms.sendMessage(Mods.Names.CHISEL, "add_variation", tag);
+    }
+
+    private boolean doesGroupExist(String group) {
+        return Carving.chisel.getGroup(group) != null;
+    }
+}
diff --git a/src/main/java/gregtech/integration/crafttweaker/CraftTweakerModule.java b/src/main/java/gregtech/integration/crafttweaker/CraftTweakerModule.java
index 400c41a1110..37fb6535fe5 100644
--- a/src/main/java/gregtech/integration/crafttweaker/CraftTweakerModule.java
+++ b/src/main/java/gregtech/integration/crafttweaker/CraftTweakerModule.java
@@ -4,6 +4,7 @@
 import gregtech.api.items.metaitem.MetaOreDictItem;
 import gregtech.api.modules.GregTechModule;
 import gregtech.api.unification.material.event.MaterialEvent;
+import gregtech.api.util.Mods;
 import gregtech.integration.IntegrationModule;
 import gregtech.integration.IntegrationSubmodule;
 import gregtech.integration.crafttweaker.recipe.MetaItemBracketHandler;
@@ -27,7 +28,7 @@
 @GregTechModule(
                 moduleID = GregTechModules.MODULE_CT,
                 containerID = GTValues.MODID,
-                modDependencies = GTValues.MODID_CT,
+                modDependencies = Mods.Names.CRAFT_TWEAKER,
                 name = "GregTech CraftTweaker Integration",
                 description = "CraftTweaker Integration Module")
 public class CraftTweakerModule extends IntegrationSubmodule {
diff --git a/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialBuilder.java b/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialBuilder.java
index b8dc8e77a9b..d12a134adc5 100644
--- a/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialBuilder.java
+++ b/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialBuilder.java
@@ -1,6 +1,5 @@
 package gregtech.integration.crafttweaker.material;
 
-import gregtech.api.GTValues;
 import gregtech.api.fluids.FluidBuilder;
 import gregtech.api.fluids.FluidState;
 import gregtech.api.fluids.store.FluidStorageKey;
@@ -14,6 +13,7 @@
 import gregtech.api.unification.material.properties.ToolProperty;
 import gregtech.api.unification.stack.MaterialStack;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.Mods;
 
 import net.minecraft.enchantment.Enchantment;
 
@@ -264,7 +264,7 @@ public CTMaterialBuilder itemPipeProperties(int priority, float stacksPerSec) {
     }
 
     @ZenMethod
-    @net.minecraftforge.fml.common.Optional.Method(modid = GTValues.MODID_CT)
+    @net.minecraftforge.fml.common.Optional.Method(modid = Mods.Names.CRAFT_TWEAKER)
     public CTMaterialBuilder addDefaultEnchant(IEnchantment enchantment) {
         Enchantment enchantmentType = (Enchantment) enchantment.getDefinition().getInternal();
         backingBuilder.addDefaultEnchant(enchantmentType, enchantment.getLevel());
diff --git a/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialHelpers.java b/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialHelpers.java
index ca5d08a1efa..ed66c077a40 100644
--- a/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialHelpers.java
+++ b/src/main/java/gregtech/integration/crafttweaker/material/CTMaterialHelpers.java
@@ -4,6 +4,7 @@
 import gregtech.api.fluids.FluidState;
 import gregtech.api.unification.material.Material;
 import gregtech.api.unification.stack.MaterialStack;
+import gregtech.integration.groovy.GroovyScriptModule;
 
 import com.google.common.collect.ImmutableList;
 import crafttweaker.CraftTweakerAPI;
@@ -17,7 +18,9 @@ protected static ImmutableList validateComponentList(MaterialStac
     protected static FluidState validateFluidState(String fluidTypeName) {
         if (fluidTypeName == null || fluidTypeName.equals("fluid"))
             return FluidState.LIQUID;
-
+        if (GroovyScriptModule.isCurrentlyRunning()) {
+            return GroovyScriptModule.parseAndValidateEnumValue(FluidState.class, fluidTypeName, "fluid type");
+        }
         if (fluidTypeName.equals("liquid")) return FluidState.LIQUID;
         if (fluidTypeName.equals("gas")) return FluidState.GAS;
         if (fluidTypeName.equals("plasma")) return FluidState.PLASMA;
diff --git a/src/main/java/gregtech/integration/crafttweaker/material/MaterialExpansion.java b/src/main/java/gregtech/integration/crafttweaker/material/MaterialExpansion.java
index 3f8e854cb29..aeef7c2ac07 100644
--- a/src/main/java/gregtech/integration/crafttweaker/material/MaterialExpansion.java
+++ b/src/main/java/gregtech/integration/crafttweaker/material/MaterialExpansion.java
@@ -1,11 +1,11 @@
 package gregtech.integration.crafttweaker.material;
 
-import gregtech.api.GTValues;
 import gregtech.api.fluids.store.FluidStorageKeys;
 import gregtech.api.unification.material.Material;
 import gregtech.api.unification.material.info.MaterialFlag;
 import gregtech.api.unification.material.info.MaterialIconSet;
 import gregtech.api.unification.material.properties.*;
+import gregtech.api.util.Mods;
 
 import net.minecraft.enchantment.Enchantment;
 
@@ -65,7 +65,7 @@ public static boolean isGaseous(Material m) {
 
     // TODO May need to move this to Material
     @ZenGetter("fluid")
-    @net.minecraftforge.fml.common.Optional.Method(modid = GTValues.MODID_CT)
+    @net.minecraftforge.fml.common.Optional.Method(modid = Mods.Names.CRAFT_TWEAKER)
     public static ILiquidDefinition getFluid(Material m) {
         FluidProperty prop = m.getProperty(PropertyKey.FLUID);
         if (prop != null) {
@@ -164,13 +164,13 @@ public static int toolEnchant(Material m) {
     }
 
     @ZenMethod
-    @net.minecraftforge.fml.common.Optional.Method(modid = GTValues.MODID_CT)
+    @net.minecraftforge.fml.common.Optional.Method(modid = Mods.Names.CRAFT_TWEAKER)
     public static void addToolEnchantment(Material m, IEnchantment enchantment) {
         addScaledToolEnchantment(m, enchantment, 0);
     }
 
     @ZenMethod
-    @net.minecraftforge.fml.common.Optional.Method(modid = GTValues.MODID_CT)
+    @net.minecraftforge.fml.common.Optional.Method(modid = Mods.Names.CRAFT_TWEAKER)
     public static void addScaledToolEnchantment(Material m, IEnchantment enchantment, double levelGrowth) {
         if (checkFrozen("add tool enchantment")) return;
         ToolProperty prop = m.getProperty(PropertyKey.TOOL);
diff --git a/src/main/java/gregtech/integration/ctm/IFacadeWrapper.java b/src/main/java/gregtech/integration/ctm/IFacadeWrapper.java
index 76d76eb64c9..287b9a9e647 100644
--- a/src/main/java/gregtech/integration/ctm/IFacadeWrapper.java
+++ b/src/main/java/gregtech/integration/ctm/IFacadeWrapper.java
@@ -1,6 +1,6 @@
 package gregtech.integration.ctm;
 
-import gregtech.api.GTValues;
+import gregtech.api.util.Mods;
 
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.util.EnumFacing;
@@ -11,7 +11,7 @@
 import org.jetbrains.annotations.NotNull;
 import team.chisel.ctm.api.IFacade;
 
-@Optional.Interface(modid = GTValues.MODID_CTM, iface = "team.chisel.ctm.api.IFacade")
+@Optional.Interface(modid = Mods.Names.CONNECTED_TEXTURES_MOD, iface = "team.chisel.ctm.api.IFacade")
 public interface IFacadeWrapper extends IFacade {
 
     @NotNull
diff --git a/src/main/java/gregtech/integration/forestry/ForestryModule.java b/src/main/java/gregtech/integration/forestry/ForestryModule.java
index edd4080c33b..b938f4ab228 100644
--- a/src/main/java/gregtech/integration/forestry/ForestryModule.java
+++ b/src/main/java/gregtech/integration/forestry/ForestryModule.java
@@ -7,12 +7,15 @@
 import gregtech.api.items.toolitem.ItemGTTool;
 import gregtech.api.modules.GregTechModule;
 import gregtech.api.recipes.machines.RecipeMapScanner;
+import gregtech.api.unification.OreDictUnifier;
 import gregtech.api.unification.material.Material;
 import gregtech.api.unification.material.Materials;
 import gregtech.api.unification.material.event.MaterialEvent;
 import gregtech.api.unification.material.info.MaterialFlags;
 import gregtech.api.unification.material.properties.OreProperty;
 import gregtech.api.unification.material.properties.PropertyKey;
+import gregtech.api.unification.ore.OrePrefix;
+import gregtech.api.util.Mods;
 import gregtech.common.items.ToolItems;
 import gregtech.integration.IntegrationModule;
 import gregtech.integration.IntegrationSubmodule;
@@ -25,10 +28,10 @@
 
 import net.minecraft.client.Minecraft;
 import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
 import net.minecraft.item.crafting.IRecipe;
 import net.minecraftforge.client.event.ModelRegistryEvent;
 import net.minecraftforge.event.RegistryEvent;
-import net.minecraftforge.fml.common.Loader;
 import net.minecraftforge.fml.common.event.FMLInitializationEvent;
 import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
 import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
@@ -41,13 +44,15 @@
 import forestry.core.items.IColoredItem;
 import org.jetbrains.annotations.NotNull;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.Collections;
 import java.util.List;
 
 @GregTechModule(
                 moduleID = GregTechModules.MODULE_FR,
                 containerID = GTValues.MODID,
-                modDependencies = GTValues.MODID_FR,
+                modDependencies = Mods.Names.FORESTRY,
                 name = "GregTech Forestry Integration",
                 description = "Forestry Integration Module")
 public class ForestryModule extends IntegrationSubmodule {
@@ -95,7 +100,7 @@ public void preInit(FMLPreInitializationEvent event) {
 
         // GT Frames
         if (ForestryConfig.enableGTFrames) {
-            if (ForestryUtil.apicultureEnabled()) {
+            if (Mods.ForestryApiculture.isModLoaded()) {
                 FRAME_ACCELERATED = new GTItemFrame(GTFrameType.ACCELERATED);
                 FRAME_MUTAGENIC = new GTItemFrame(GTFrameType.MUTAGENIC);
                 FRAME_WORKING = new GTItemFrame(GTFrameType.WORKING);
@@ -121,7 +126,7 @@ public void preInit(FMLPreInitializationEvent event) {
 
         // GT Bees
         if (ForestryConfig.enableGTBees) {
-            if (ForestryUtil.apicultureEnabled()) {
+            if (Mods.ForestryApiculture.isModLoaded()) {
                 DROPS = new GTDropItem();
                 COMBS = new GTCombItem();
             } else {
@@ -132,7 +137,7 @@ public void preInit(FMLPreInitializationEvent event) {
         // Remove duplicate/conflicting bees from other Forestry addons.
         // Done in init to have our changes applied before their registration,
         // since we load after other Forestry addons purposefully.
-        if (ForestryConfig.disableConflictingBees && ForestryUtil.apicultureEnabled()) {
+        if (ForestryConfig.disableConflictingBees && Mods.ForestryApiculture.isModLoaded()) {
             BeeRemovals.init();
         }
 
@@ -149,7 +154,7 @@ public void init(FMLInitializationEvent event) {
             ForestryElectrodeRecipes.onInit();
         }
 
-        if (ForestryUtil.apicultureEnabled()) {
+        if (Mods.ForestryApiculture.isModLoaded()) {
             if (ForestryConfig.harderForestryRecipes) {
                 ForestryMiscRecipes.initRemoval();
             }
@@ -160,8 +165,12 @@ public void init(FMLInitializationEvent event) {
             }
         }
 
+        if (Mods.ExtraBees.isModLoaded()) {
+            registerAlvearyMutators();
+        }
+
         if (event.getSide() == Side.CLIENT) {
-            if (ForestryUtil.apicultureEnabled()) {
+            if (Mods.ForestryApiculture.isModLoaded()) {
                 if (ForestryConfig.enableGTBees) {
                     Minecraft.getMinecraft().getItemColors().registerItemColorHandler((stack, tintIndex) -> {
                         if (stack.getItem() instanceof IColoredItem coloredItem) {
@@ -176,7 +185,7 @@ public void init(FMLInitializationEvent event) {
 
     @Override
     public void postInit(FMLPostInitializationEvent event) {
-        if (ForestryUtil.apicultureEnabled()) {
+        if (Mods.ForestryApiculture.isModLoaded()) {
             getLogger().info("Copying Forestry Centrifuge recipes to GT Centrifuge");
             CombRecipes.initForestryCombs();
         }
@@ -187,7 +196,7 @@ public static void registerItems(RegistryEvent.Register event) {
         IForgeRegistry registry = event.getRegistry();
 
         // GT Frames
-        if (ForestryUtil.apicultureEnabled()) {
+        if (Mods.ForestryApiculture.isModLoaded()) {
             if (ForestryConfig.enableGTFrames) {
                 registry.register(FRAME_ACCELERATED);
                 registry.register(FRAME_MUTAGENIC);
@@ -213,20 +222,19 @@ public static void registerItems(RegistryEvent.Register event) {
             ELECTRODE_OBSIDIAN = forestryMetaItem.addItem(10, "electrode.obsidian");
             ELECTRODE_TIN = forestryMetaItem.addItem(11, "electrode.tin");
 
-            if (Loader.isModLoaded(GTValues.MODID_IC2) || Loader.isModLoaded(GTValues.MODID_BINNIE)) {
+            if (Mods.IndustrialCraft2.isModLoaded() || Mods.BinnieCore.isModLoaded()) {
                 ELECTRODE_IRON = forestryMetaItem.addItem(12, "electrode.iron");
             }
-            if (Loader.isModLoaded(GTValues.MODID_XU2)) {
+            if (Mods.ExtraUtilities2.isModLoaded()) {
                 ELECTRODE_ORCHID = forestryMetaItem.addItem(13, "electrode.orchid");
             }
-            if (Loader.isModLoaded(GTValues.MODID_IC2) || Loader.isModLoaded(GTValues.MODID_TR) ||
-                    Loader.isModLoaded(GTValues.MODID_BINNIE)) {
+            if (Mods.IndustrialCraft2.isModLoaded() || Mods.TechReborn.isModLoaded() || Mods.BinnieCore.isModLoaded()) {
                 ELECTRODE_RUBBER = forestryMetaItem.addItem(14, "electrode.rubber");
             }
         }
 
         // GT Drops
-        if (ForestryUtil.apicultureEnabled()) {
+        if (Mods.ForestryApiculture.isModLoaded()) {
             if (ForestryConfig.enableGTBees) {
                 registry.register(DROPS);
                 registry.register(COMBS);
@@ -237,7 +245,7 @@ public static void registerItems(RegistryEvent.Register event) {
     @SideOnly(Side.CLIENT)
     @SubscribeEvent
     public static void registerModels(ModelRegistryEvent event) {
-        if (ForestryUtil.apicultureEnabled()) {
+        if (Mods.ForestryApiculture.isModLoaded()) {
             if (ForestryConfig.enableGTFrames) {
                 FRAME_ACCELERATED.registerModel(FRAME_ACCELERATED, ForestryAPI.modelManager);
                 FRAME_MUTAGENIC.registerModel(FRAME_MUTAGENIC, ForestryAPI.modelManager);
@@ -256,7 +264,7 @@ public static void registerModels(ModelRegistryEvent event) {
 
     @SubscribeEvent
     public static void registerRecipes(RegistryEvent.Register event) {
-        if (ForestryUtil.apicultureEnabled()) {
+        if (Mods.ForestryApiculture.isModLoaded()) {
             // GT Frames
             if (ForestryConfig.enableGTFrames) {
                 ForestryFrameRecipes.init();
@@ -285,7 +293,7 @@ public static void registerRecipes(RegistryEvent.Register event) {
 
     @SubscribeEvent
     public static void registerMaterials(MaterialEvent event) {
-        if (ForestryUtil.apicultureEnabled()) {
+        if (Mods.ForestryApiculture.isModLoaded()) {
             if (ForestryConfig.enableGTFrames) {
                 Materials.TreatedWood.addFlags(MaterialFlags.GENERATE_LONG_ROD);
                 Materials.Uranium235.addFlags(MaterialFlags.GENERATE_LONG_ROD);
@@ -344,4 +352,31 @@ private static void createOreProperty(Material material, Material... byproducts)
         material.setProperty(PropertyKey.ORE, property);
         material.addFlags(MaterialFlags.DISABLE_ORE_BLOCK);
     }
+
+    private static void registerAlvearyMutators() {
+        try {
+            Class mutationHandler = Class.forName("binnie.extrabees.utils.AlvearyMutationHandler");
+            Method method = mutationHandler.getDeclaredMethod("addMutationItem", ItemStack.class, float.class);
+
+            registerAlvearyMutator(method, Materials.Uranium238, 2.0f);
+            registerAlvearyMutator(method, Materials.Uranium235, 4.0f);
+            registerAlvearyMutator(method, Materials.Plutonium241, 6.0f);
+            registerAlvearyMutator(method, Materials.Plutonium239, 8.0f);
+            registerAlvearyMutator(method, Materials.NaquadahEnriched, 10.0f);
+            registerAlvearyMutator(method, Materials.Naquadria, 15.0f);
+        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {
+            IntegrationModule.logger.error("Could not register GT Alveary mutators!");
+        }
+    }
+
+    private static void registerAlvearyMutator(Method method, Material material, float chance) {
+        try {
+            ItemStack stack = OreDictUnifier.get(OrePrefix.dust, material);
+            if (stack != ItemStack.EMPTY) {
+                method.invoke(null, stack, chance);
+            }
+        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+            IntegrationModule.logger.error("Could not register GT Alveary mutators!");
+        }
+    }
 }
diff --git a/src/main/java/gregtech/integration/forestry/ForestryUtil.java b/src/main/java/gregtech/integration/forestry/ForestryUtil.java
index 32fff8e1e19..1bc2a8df6c0 100644
--- a/src/main/java/gregtech/integration/forestry/ForestryUtil.java
+++ b/src/main/java/gregtech/integration/forestry/ForestryUtil.java
@@ -1,6 +1,6 @@
 package gregtech.integration.forestry;
 
-import gregtech.api.GTValues;
+import gregtech.api.util.Mods;
 import gregtech.integration.IntegrationModule;
 import gregtech.integration.forestry.bees.GTCombType;
 import gregtech.integration.forestry.bees.GTDropType;
@@ -11,52 +11,39 @@
 import forestry.api.apiculture.IAlleleBeeSpecies;
 import forestry.api.genetics.AlleleManager;
 import forestry.api.genetics.IAlleleFlowers;
-import forestry.modules.ModuleHelper;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public class ForestryUtil {
 
-    public static boolean apicultureEnabled() {
-        return ModuleHelper.isEnabled("apiculture");
-    }
-
-    public static boolean arboricultureEnabled() {
-        return ModuleHelper.isEnabled("arboriculture");
-    }
-
-    public static boolean lepidopterologyEnabled() {
-        return ModuleHelper.isEnabled("lepidopterology");
-    }
-
     @Nullable
-    public static IAlleleBeeEffect getEffect(@NotNull String modid, @NotNull String name) {
-        String s = switch (modid) {
-            case GTValues.MODID_EB -> "extrabees.effect." + name;
-            case GTValues.MODID_MB -> "magicbees.effect" + name;
-            case GTValues.MODID -> "gregtech.effect." + name;
+    public static IAlleleBeeEffect getEffect(@NotNull Mods mod, @NotNull String name) {
+        String s = switch (mod) {
+            case ExtraBees -> "extrabees.effect." + name;
+            case MagicBees -> "magicbees.effect" + name;
+            case GregTech -> "gregtech.effect." + name;
             default -> "forestry.effect" + name;
         };
         return (IAlleleBeeEffect) AlleleManager.alleleRegistry.getAllele(s);
     }
 
     @Nullable
-    public static IAlleleFlowers getFlowers(@NotNull String modid, @NotNull String name) {
-        String s = switch (modid) {
-            case GTValues.MODID_EB -> "extrabees.flower." + name;
-            case GTValues.MODID_MB -> "magicbees.flower" + name;
-            case GTValues.MODID -> "gregtech.flower." + name;
+    public static IAlleleFlowers getFlowers(@NotNull Mods mod, @NotNull String name) {
+        String s = switch (mod) {
+            case ExtraBees -> "extrabees.flower." + name;
+            case MagicBees -> "magicbees.flower" + name;
+            case GregTech -> "gregtech.flower." + name;
             default -> "forestry.flowers" + name;
         };
         return (IAlleleFlowers) AlleleManager.alleleRegistry.getAllele(s);
     }
 
     @Nullable
-    public static IAlleleBeeSpecies getSpecies(@NotNull String modid, @NotNull String name) {
-        String s = switch (modid) {
-            case GTValues.MODID_EB -> "extrabees.species." + name;
-            case GTValues.MODID_MB -> "magicbees.species" + name;
-            case GTValues.MODID -> "gregtech.species." + name;
+    public static IAlleleBeeSpecies getSpecies(@NotNull Mods mod, @NotNull String name) {
+        String s = switch (mod) {
+            case ExtraBees -> "extrabees.species." + name;
+            case MagicBees -> "magicbees.species" + name;
+            case GregTech -> "gregtech.species." + name;
             default -> "forestry.species" + name;
         };
         return (IAlleleBeeSpecies) AlleleManager.alleleRegistry.getAllele(s);
@@ -74,7 +61,7 @@ public static ItemStack getCombStack(@NotNull GTCombType type, int amount) {
                     .error("Tried to get GregTech Comb stack, but GregTech Bees config is not enabled!");
             return ItemStack.EMPTY;
         }
-        if (!apicultureEnabled()) {
+        if (!Mods.ForestryApiculture.isModLoaded()) {
             IntegrationModule.logger.error("Tried to get GregTech Comb stack, but Apiculture module is not enabled!");
             return ItemStack.EMPTY;
         }
@@ -93,7 +80,7 @@ public static ItemStack getDropStack(@NotNull GTDropType type, int amount) {
                     .error("Tried to get GregTech Drop stack, but GregTech Bees config is not enabled!");
             return ItemStack.EMPTY;
         }
-        if (!apicultureEnabled()) {
+        if (!Mods.ForestryApiculture.isModLoaded()) {
             IntegrationModule.logger.error("Tried to get GregTech Drop stack, but Apiculture module is not enabled!");
             return ItemStack.EMPTY;
         }
diff --git a/src/main/java/gregtech/integration/forestry/bees/BeeRemovals.java b/src/main/java/gregtech/integration/forestry/bees/BeeRemovals.java
index 65546dfe020..39dcb08e103 100644
--- a/src/main/java/gregtech/integration/forestry/bees/BeeRemovals.java
+++ b/src/main/java/gregtech/integration/forestry/bees/BeeRemovals.java
@@ -1,10 +1,8 @@
 package gregtech.integration.forestry.bees;
 
-import gregtech.api.GTValues;
+import gregtech.api.util.Mods;
 import gregtech.integration.IntegrationModule;
 
-import net.minecraftforge.fml.common.Loader;
-
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
@@ -16,10 +14,10 @@ public class BeeRemovals {
     private static final List EB_REMOVALS = new ArrayList<>();
 
     public static void init() {
-        if (Loader.isModLoaded(GTValues.MODID_MB)) {
+        if (Mods.MagicBees.isModLoaded()) {
             removeMagicBees();
         }
-        if (Loader.isModLoaded(GTValues.MODID_EB)) {
+        if (Mods.ExtraBees.isModLoaded()) {
             removeExtraBees();
         }
     }
diff --git a/src/main/java/gregtech/integration/forestry/bees/ForestryScannerLogic.java b/src/main/java/gregtech/integration/forestry/bees/ForestryScannerLogic.java
index 891a8277bc9..0bd8d25bf53 100644
--- a/src/main/java/gregtech/integration/forestry/bees/ForestryScannerLogic.java
+++ b/src/main/java/gregtech/integration/forestry/bees/ForestryScannerLogic.java
@@ -3,7 +3,7 @@
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.machines.IScannerRecipeMap;
-import gregtech.integration.forestry.ForestryUtil;
+import gregtech.api.util.Mods;
 
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
@@ -60,7 +60,7 @@ public List getRepresentativeRecipes() {
         List recipes = new ArrayList<>();
         ItemStack outputStack;
 
-        if (ForestryUtil.apicultureEnabled()) {
+        if (Mods.ForestryApiculture.isModLoaded()) {
             outputStack = ModuleApiculture.getItems().beeDroneGE.getItemStack();
             outputStack.setTagCompound(BeeDefinition.COMMON.getIndividual().writeToNBT(new NBTTagCompound()));
             outputStack.setTranslatableName("gregtech.scanner.forestry.drone");
@@ -98,7 +98,7 @@ public List getRepresentativeRecipes() {
                     .duration(DURATION).EUt(EUT).build().getResult());
         }
 
-        if (ForestryUtil.arboricultureEnabled()) {
+        if (Mods.ForestryArboriculture.isModLoaded()) {
             outputStack = ModuleArboriculture.getItems().sapling.getItemStack();
             outputStack.setTagCompound(TreeDefinition.Oak.getIndividual().writeToNBT(new NBTTagCompound()));
             outputStack.setTranslatableName("gregtech.scanner.forestry.sapling");
@@ -118,7 +118,7 @@ public List getRepresentativeRecipes() {
                     .duration(DURATION).EUt(EUT).build().getResult());
         }
 
-        if (ForestryUtil.lepidopterologyEnabled()) {
+        if (Mods.ForestryLepidopterology.isModLoaded()) {
             outputStack = ModuleLepidopterology.getItems().butterflyGE.getItemStack();
             outputStack
                     .setTagCompound(ButterflyDefinition.CabbageWhite.getIndividual().writeToNBT(new NBTTagCompound()));
diff --git a/src/main/java/gregtech/integration/forestry/bees/GTBeeDefinition.java b/src/main/java/gregtech/integration/forestry/bees/GTBeeDefinition.java
index 827eb307c28..d4a967e4bfe 100644
--- a/src/main/java/gregtech/integration/forestry/bees/GTBeeDefinition.java
+++ b/src/main/java/gregtech/integration/forestry/bees/GTBeeDefinition.java
@@ -5,8 +5,9 @@
 import gregtech.api.unification.material.Materials;
 import gregtech.api.unification.ore.OrePrefix;
 import gregtech.api.unification.stack.UnificationEntry;
+import gregtech.api.util.Mods;
+import gregtech.common.blocks.MetaBlocks;
 import gregtech.common.items.MetaItems;
-import gregtech.integration.IntegrationUtil;
 import gregtech.integration.forestry.ForestryModule;
 import gregtech.integration.forestry.ForestryUtil;
 
@@ -14,7 +15,6 @@
 import net.minecraft.init.Items;
 import net.minecraft.item.ItemStack;
 import net.minecraftforge.common.BiomeDictionary;
-import net.minecraftforge.fml.common.Loader;
 import net.minecraftforge.fml.common.Optional;
 
 import appeng.core.Api;
@@ -47,7 +47,7 @@ public enum GTBeeDefinition implements IBeeDefinition {
     // Organic
     CLAY(GTBranchDefinition.GT_ORGANIC, "Lutum", true, 0xC8C8DA, 0x0000FF,
             beeSpecies -> {
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
+                if (Mods.ExtraBees.isModLoaded()) {
                     beeSpecies.addProduct(getExtraBeesComb(22), 0.30f); // CLAY
                 } else {
                     beeSpecies.addProduct(getForestryComb(EnumHoneyComb.HONEY), 0.30f);
@@ -55,8 +55,8 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 beeSpecies.addProduct(new ItemStack(Items.CLAY_BALL), 0.15f);
                 beeSpecies.setHumidity(EnumHumidity.DAMP);
                 beeSpecies.setTemperature(EnumTemperature.NORMAL);
-                if (Loader.isModLoaded(GTValues.MODID_BOP)) {
-                    beeSpecies.addSpecialty(IntegrationUtil.getModItem(GTValues.MODID_BOP, "mudball", 0), 0.05f);
+                if (Mods.BiomesOPlenty.isModLoaded()) {
+                    beeSpecies.addSpecialty(Mods.BiomesOPlenty.getItem("mudball", 0), 0.05f);
                 }
             },
             template -> {
@@ -76,10 +76,9 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 beeSpecies.addSpecialty(getGTComb(GTCombType.STICKY), 0.05f);
                 beeSpecies.setHumidity(EnumHumidity.DAMP);
                 beeSpecies.setTemperature(EnumTemperature.NORMAL);
-                if (Loader.isModLoaded(GTValues.MODID_TCON)) {
-                    beeSpecies.addProduct(IntegrationUtil.getModItem(GTValues.MODID_TCON, "edible", 1), 0.10f);
-                    beeSpecies.addSpecialty(IntegrationUtil.getModItem(GTValues.MODID_TCON, "slime_congealed", 2),
-                            0.01f);
+                if (Mods.TinkersConstruct.isModLoaded()) {
+                    beeSpecies.addProduct(Mods.TinkersConstruct.getItem("edible", 1), 0.10f);
+                    beeSpecies.addSpecialty(Mods.TinkersConstruct.getItem("slime_congealed", 2), 0.01f);
                 } else {
                     beeSpecies.addSpecialty(new ItemStack(Blocks.SLIME_BLOCK), 0.01f);
                 }
@@ -89,9 +88,9 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 AlleleHelper.getInstance().set(template, FLOWERING, EnumAllele.Flowering.SLOWER);
                 AlleleHelper.getInstance().set(template, TEMPERATURE_TOLERANCE, EnumAllele.Tolerance.BOTH_1);
                 AlleleHelper.getInstance().set(template, HUMIDITY_TOLERANCE, EnumAllele.Tolerance.BOTH_1);
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
+                if (Mods.ExtraBees.isModLoaded()) {
                     AlleleHelper.getInstance().set(template, FLOWER_PROVIDER,
-                            ForestryUtil.getFlowers(GTValues.MODID_EB, "water"));
+                            ForestryUtil.getFlowers(Mods.ExtraBees, "water"));
                 }
             },
             dis -> {
@@ -166,15 +165,15 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 AlleleHelper.getInstance().set(template, SPEED, EnumAllele.Speed.SLOWER);
                 AlleleHelper.getInstance().set(template, TEMPERATURE_TOLERANCE, EnumAllele.Tolerance.NONE);
                 AlleleHelper.getInstance().set(template, HUMIDITY_TOLERANCE, EnumAllele.Tolerance.NONE);
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
+                if (Mods.ExtraBees.isModLoaded()) {
                     AlleleHelper.getInstance().set(template, FLOWER_PROVIDER,
-                            ForestryUtil.getFlowers(GTValues.MODID_EB, "water"));
+                            ForestryUtil.getFlowers(Mods.ExtraBees, "water"));
                 }
             },
             dis -> dis.registerMutation(COAL, STICKYRESIN, 4)),
     ASH(GTBranchDefinition.GT_ORGANIC, "Cinis", true, 0x1E1A18, 0xC6C6C6,
             beeSpecies -> {
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
+                if (Mods.ExtraBees.isModLoaded()) {
                     beeSpecies.addProduct(getExtraBeesComb(9), 0.30f); // SEED
                 } else {
                     beeSpecies.addProduct(getForestryComb(EnumHoneyComb.HONEY), 0.30f);
@@ -196,7 +195,7 @@ public enum GTBeeDefinition implements IBeeDefinition {
             }),
     APATITE(GTBranchDefinition.GT_ORGANIC, "Stercorat", true, 0x7FCEF5, 0x654525,
             beeSpecies -> {
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
+                if (Mods.ExtraBees.isModLoaded()) {
                     beeSpecies.addProduct(getExtraBeesComb(9), 0.15f); // SEED
                 } else {
                     beeSpecies.addProduct(getForestryComb(EnumHoneyComb.HONEY), 0.15f);
@@ -210,9 +209,9 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 AlleleHelper.getInstance().set(template, LIFESPAN, EnumAllele.Lifespan.LONGER);
                 AlleleHelper.getInstance().set(template, FLOWER_PROVIDER, EnumAllele.Flowers.WHEAT);
                 AlleleHelper.getInstance().set(template, FLOWERING, EnumAllele.Flowering.FASTER);
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
+                if (Mods.ExtraBees.isModLoaded()) {
                     AlleleHelper.getInstance().set(template, FLOWER_PROVIDER,
-                            ForestryUtil.getFlowers(GTValues.MODID_EB, "rock"));
+                            ForestryUtil.getFlowers(Mods.ExtraBees, "rock"));
                 }
             },
             dis -> {
@@ -238,7 +237,7 @@ public enum GTBeeDefinition implements IBeeDefinition {
             }),
     FERTILIZER(GTBranchDefinition.GT_ORGANIC, "Stercorat", true, 0x7FCEF5, 0x654525,
             beeSpecies -> {
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
+                if (Mods.ExtraBees.isModLoaded()) {
                     beeSpecies.addProduct(getExtraBeesComb(9), 0.15f); // SEED
                 } else {
                     beeSpecies.addProduct(getForestryComb(EnumHoneyComb.MOSSY), 0.15f);
@@ -246,7 +245,7 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 beeSpecies.addSpecialty(OreDictUnifier.get(OrePrefix.dustTiny, Materials.Ash), 0.2f);
                 beeSpecies.addSpecialty(OreDictUnifier.get(OrePrefix.dustTiny, Materials.DarkAsh), 0.2f);
                 beeSpecies.addSpecialty(MetaItems.FERTILIZER.getStackForm(), 0.3f);
-                beeSpecies.addSpecialty(IntegrationUtil.getModItem(GTValues.MODID_FR, "fertilizer_compound", 0), 0.3f);
+                beeSpecies.addSpecialty(Mods.Forestry.getItem("fertilizer_compound", 0), 0.3f);
                 beeSpecies.setHumidity(EnumHumidity.DAMP);
                 beeSpecies.setTemperature(EnumTemperature.WARM);
             },
@@ -272,16 +271,12 @@ public enum GTBeeDefinition implements IBeeDefinition {
             }),
     SANDWICH(GTBranchDefinition.GT_ORGANIC, "Sandwico", true, 0x32CD32, 0xDAA520,
             beeSpecies -> {
-                beeSpecies.addProduct(IntegrationUtil.getModItem(GTValues.MODID_GTFO, "gtfo_meta_item", 81), 0.05f); // Cucumber
-                                                                                                                     // Slice
-                beeSpecies.addProduct(IntegrationUtil.getModItem(GTValues.MODID_GTFO, "gtfo_meta_item", 80), 0.05f); // Onion
-                                                                                                                     // Slice
-                beeSpecies.addProduct(IntegrationUtil.getModItem(GTValues.MODID_GTFO, "gtfo_meta_item", 79), 0.05f); // Tomato
-                                                                                                                     // Slice
+                beeSpecies.addProduct(Mods.GregTechFoodOption.getItem("gtfo_meta_item", 81), 0.05f); // Cucumber Slice
+                beeSpecies.addProduct(Mods.GregTechFoodOption.getItem("gtfo_meta_item", 80), 0.05f); // Onion Slice
+                beeSpecies.addProduct(Mods.GregTechFoodOption.getItem("gtfo_meta_item", 79), 0.05f); // Tomato Slice
                 beeSpecies.addSpecialty(new ItemStack(Items.COOKED_PORKCHOP), 0.05f);
                 beeSpecies.addSpecialty(new ItemStack(Items.COOKED_BEEF), 0.15f);
-                beeSpecies.addSpecialty(IntegrationUtil.getModItem(GTValues.MODID_GTFO, "gtfo_meta_item", 97), 0.05f); // Cheddar
-                                                                                                                       // Slice
+                beeSpecies.addSpecialty(Mods.GregTechFoodOption.getItem("gtfo_meta_item", 97), 0.05f); // Cheddar Slice
                 beeSpecies.setHumidity(EnumHumidity.NORMAL);
                 beeSpecies.setTemperature(EnumTemperature.NORMAL);
             },
@@ -295,14 +290,13 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 AlleleHelper.getInstance().set(template, FLOWERING, EnumAllele.Flowering.FASTER);
             },
             dis -> {
-                if (Loader.isModLoaded(GTValues.MODID_MB)) {
-                    dis.registerMutation(BeeDefinition.AGRARIAN, ForestryUtil.getSpecies(GTValues.MODID_MB, "Batty"),
-                            10);
+                if (Mods.MagicBees.isModLoaded()) {
+                    dis.registerMutation(BeeDefinition.AGRARIAN, ForestryUtil.getSpecies(Mods.MagicBees, "Batty"), 10);
                 } else {
                     dis.registerMutation(BeeDefinition.AGRARIAN, BeeDefinition.IMPERIAL, 10);
                 }
             },
-            () -> Loader.isModLoaded(GTValues.MODID_GTFO)),
+            Mods.GregTechFoodOption::isModLoaded),
 
     // Gems
     REDSTONE(GTBranchDefinition.GT_GEM, "Rubrumlapis", true, 0x7D0F0F, 0xD11919,
@@ -356,7 +350,7 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 Api.INSTANCE.definitions().blocks().fluixBlock().maybeBlock()
                         .ifPresent(block -> mutation.requireResource(block.getDefaultState()));
             },
-            () -> Loader.isModLoaded(GTValues.MODID_APPENG)),
+            Mods.AppliedEnergistics2::isModLoaded),
     DIAMOND(GTBranchDefinition.GT_GEM, "Adamas", false, 0xCCFFFF, 0xA3CCCC,
             beeSpecies -> {
                 beeSpecies.addProduct(getGTComb(GTCombType.STONE), 0.30f);
@@ -421,7 +415,7 @@ public enum GTBeeDefinition implements IBeeDefinition {
             }),
     SPARKLING(GTBranchDefinition.GT_GEM, "Vesperstella", true, 0x7A007A, 0xFFFFFF,
             beeSpecies -> {
-                beeSpecies.addProduct(IntegrationUtil.getModItem(GTValues.MODID_MB, "resource", 3), 0.20f);
+                beeSpecies.addProduct(Mods.MagicBees.getItem("resource", 3), 0.20f);
                 beeSpecies.addSpecialty(getGTComb(GTCombType.SPARKLING), 0.125f);
                 beeSpecies.setHumidity(EnumHumidity.NORMAL);
                 beeSpecies.setTemperature(EnumTemperature.NORMAL);
@@ -437,12 +431,12 @@ public enum GTBeeDefinition implements IBeeDefinition {
             },
             dis -> {
                 IBeeMutationBuilder mutation = dis.registerMutation(
-                        ForestryUtil.getSpecies(GTValues.MODID_MB, "Withering"),
-                        ForestryUtil.getSpecies(GTValues.MODID_MB, "Draconic"), 1);
+                        ForestryUtil.getSpecies(Mods.MagicBees, "Withering"),
+                        ForestryUtil.getSpecies(Mods.MagicBees, "Draconic"), 1);
                 mutation.requireResource("blockNetherStar");
                 mutation.restrictBiomeType(BiomeDictionary.Type.END);
             },
-            () -> Loader.isModLoaded(GTValues.MODID_MB)),
+            Mods.MagicBees::isModLoaded),
 
     // Metals
     COPPER(GTBranchDefinition.GT_METAL, "Cuprum", true, 0xFF6600, 0xE65C00,
@@ -587,8 +581,9 @@ public enum GTBeeDefinition implements IBeeDefinition {
                 AlleleHelper.getInstance().set(template, TOLERATES_RAIN, true);
             },
             dis -> {
-                if (Loader.isModLoaded(GTValues.MODID_MB)) {
-                    dis.registerMutation(IRON, ForestryUtil.getSpecies(GTValues.MODID_MB, "AESkystone"), 17);
+                if (Mods.MagicBees.isModLoaded() && Mods.AppliedEnergistics2.isModLoaded()) {
+                    // MB Skystone bee is only registered if AE2 is also active
+                    dis.registerMutation(IRON, ForestryUtil.getSpecies(Mods.MagicBees, "AESkystone"), 17);
                 } else {
                     dis.registerMutation(IRON, BeeDefinition.IMPERIAL, 17);
                 }
@@ -766,7 +761,7 @@ public enum GTBeeDefinition implements IBeeDefinition {
     // Industrial
     ENERGY(GTBranchDefinition.GT_INDUSTRIAL, "Industria", false, 0xC11F1F, 0xEBB9B9,
             beeSpecies -> {
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
+                if (Mods.ExtraBees.isModLoaded()) {
                     beeSpecies.addProduct(getExtraBeesComb(14), 0.30f); // STATIC
                 } else {
                     beeSpecies.addProduct(getForestryComb(EnumHoneyComb.SIMMERING), 0.30f);
@@ -787,9 +782,9 @@ public enum GTBeeDefinition implements IBeeDefinition {
             },
             dis -> {
                 IBeeMutationBuilder mutation;
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
+                if (Mods.ExtraBees.isModLoaded()) {
                     mutation = dis.registerMutation(BeeDefinition.DEMONIC,
-                            ForestryUtil.getSpecies(GTValues.MODID_EB, "volcanic"), 10);
+                            ForestryUtil.getSpecies(Mods.ExtraBees, "volcanic"), 10);
                 } else {
                     mutation = dis.registerMutation(BeeDefinition.DEMONIC, BeeDefinition.FIENDISH, 10);
                 }
@@ -820,8 +815,7 @@ public enum GTBeeDefinition implements IBeeDefinition {
             }),
     EXPLOSIVE(GTBranchDefinition.GT_INDUSTRIAL, "Explosionis", false, 0x7E270F, 0x747474,
             beeSpecies -> {
-                beeSpecies.addProduct(new ItemStack(Blocks.TNT), 0.2f);
-                // todo if we add a ITNT substitute, put it here instead of TNT
+                beeSpecies.addProduct(new ItemStack(MetaBlocks.ITNT), 0.2f);
                 beeSpecies.setHumidity(EnumHumidity.ARID);
                 beeSpecies.setTemperature(EnumTemperature.HELLISH);
                 beeSpecies.setHasEffect();
@@ -987,8 +981,8 @@ public enum GTBeeDefinition implements IBeeDefinition {
             },
             dis -> {
                 IMutationBuilder mutation;
-                if (Loader.isModLoaded(GTValues.MODID_EB)) {
-                    mutation = dis.registerMutation(THORIUM, ForestryUtil.getSpecies(GTValues.MODID_EB, "rotten"), 1);
+                if (Mods.ExtraBees.isModLoaded()) {
+                    mutation = dis.registerMutation(THORIUM, ForestryUtil.getSpecies(Mods.ExtraBees, "rotten"), 1);
                 } else {
                     mutation = dis.registerMutation(THORIUM, BeeDefinition.IMPERIAL, 1);
                 }
@@ -1040,9 +1034,9 @@ public enum GTBeeDefinition implements IBeeDefinition {
             template -> AlleleHelper.getInstance().set(template, LIFESPAN, EnumAllele.Lifespan.SHORTEST),
             dis -> {
                 IBeeMutationBuilder mutation;
-                if (Loader.isModLoaded(GTValues.MODID_MB)) {
+                if (Mods.MagicBees.isModLoaded()) {
                     mutation = dis.registerMutation(STAINLESSSTEEL,
-                            ForestryUtil.getSpecies(GTValues.MODID_MB, "Watery"), 10);
+                            ForestryUtil.getSpecies(Mods.MagicBees, "Watery"), 10);
                 } else {
                     mutation = dis.registerMutation(STAINLESSSTEEL, BeeDefinition.INDUSTRIOUS, 10);
                 }
@@ -1059,9 +1053,8 @@ public enum GTBeeDefinition implements IBeeDefinition {
             template -> AlleleHelper.getInstance().set(template, LIFESPAN, EnumAllele.Lifespan.SHORTEST),
             dis -> {
                 IBeeMutationBuilder mutation;
-                if (Loader.isModLoaded(GTValues.MODID_MB)) {
-                    mutation = dis.registerMutation(HELIUM, ForestryUtil.getSpecies(GTValues.MODID_MB, "Supernatural"),
-                            8);
+                if (Mods.MagicBees.isModLoaded()) {
+                    mutation = dis.registerMutation(HELIUM, ForestryUtil.getSpecies(Mods.MagicBees, "Supernatural"), 8);
                 } else {
                     mutation = dis.registerMutation(HELIUM, BeeDefinition.IMPERIAL, 8);
                 }
@@ -1091,9 +1084,8 @@ public enum GTBeeDefinition implements IBeeDefinition {
             template -> AlleleHelper.getInstance().set(template, LIFESPAN, EnumAllele.Lifespan.SHORTEST),
             dis -> {
                 IBeeMutationBuilder mutation;
-                if (Loader.isModLoaded(GTValues.MODID_MB)) {
-                    mutation = dis.registerMutation(NEON, ForestryUtil.getSpecies(GTValues.MODID_MB, "Supernatural"),
-                            4);
+                if (Mods.MagicBees.isModLoaded()) {
+                    mutation = dis.registerMutation(NEON, ForestryUtil.getSpecies(Mods.MagicBees, "Supernatural"), 4);
                 } else {
                     mutation = dis.registerMutation(NEON, BeeDefinition.AVENGING, 4);
                 }
@@ -1138,8 +1130,8 @@ public enum GTBeeDefinition implements IBeeDefinition {
             template -> AlleleHelper.getInstance().set(template, LIFESPAN, EnumAllele.Lifespan.SHORTEST),
             dis -> {
                 IBeeMutationBuilder mutation;
-                if (Loader.isModLoaded(GTValues.MODID_MB)) {
-                    mutation = dis.registerMutation(OXYGEN, ForestryUtil.getSpecies(GTValues.MODID_MB, "Watery"), 15);
+                if (Mods.MagicBees.isModLoaded()) {
+                    mutation = dis.registerMutation(OXYGEN, ForestryUtil.getSpecies(Mods.MagicBees, "Watery"), 15);
                 } else {
                     mutation = dis.registerMutation(OXYGEN, BeeDefinition.INDUSTRIOUS, 15);
                 }
@@ -1232,14 +1224,14 @@ private static ItemStack getForestryComb(EnumHoneyComb type) {
         return ModuleApiculture.getItems().beeComb.get(type, 1);
     }
 
-    @Optional.Method(modid = GTValues.MODID_EB)
+    @Optional.Method(modid = Mods.Names.EXTRA_BEES)
     private static ItemStack getExtraBeesComb(int meta) {
-        return IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_comb", meta);
+        return Mods.ExtraBees.getItem("honey_comb", meta);
     }
 
-    @Optional.Method(modid = GTValues.MODID_MB)
+    @Optional.Method(modid = Mods.Names.MAGIC_BEES)
     private static ItemStack getMagicBeesComb(int meta) {
-        return IntegrationUtil.getModItem(GTValues.MODID_MB, "beecomb", meta);
+        return Mods.MagicBees.getItem("beecomb", meta);
     }
 
     private static ItemStack getGTComb(GTCombType type) {
diff --git a/src/main/java/gregtech/integration/forestry/bees/GTCombItem.java b/src/main/java/gregtech/integration/forestry/bees/GTCombItem.java
index 0769ad034bf..e037872f2cf 100644
--- a/src/main/java/gregtech/integration/forestry/bees/GTCombItem.java
+++ b/src/main/java/gregtech/integration/forestry/bees/GTCombItem.java
@@ -1,6 +1,7 @@
 package gregtech.integration.forestry.bees;
 
 import gregtech.api.GTValues;
+import gregtech.api.util.Mods;
 
 import net.minecraft.client.util.ITooltipFlag;
 import net.minecraft.creativetab.CreativeTabs;
@@ -37,7 +38,7 @@ public GTCombItem() {
     public void registerModel(@NotNull Item item, IModelManager manager) {
         manager.registerItemModel(item, 0);
         for (int i = 0; i < GTCombType.values().length; i++) {
-            manager.registerItemModel(item, i, GTValues.MODID_FR, "gt.comb");
+            manager.registerItemModel(item, i, Mods.Names.FORESTRY, "gt.comb");
         }
     }
 
diff --git a/src/main/java/gregtech/integration/forestry/bees/GTCombType.java b/src/main/java/gregtech/integration/forestry/bees/GTCombType.java
index f774e6f68d0..e17fc00a429 100644
--- a/src/main/java/gregtech/integration/forestry/bees/GTCombType.java
+++ b/src/main/java/gregtech/integration/forestry/bees/GTCombType.java
@@ -1,8 +1,6 @@
 package gregtech.integration.forestry.bees;
 
-import gregtech.api.GTValues;
-
-import net.minecraftforge.fml.common.Loader;
+import gregtech.api.util.Mods;
 
 public enum GTCombType {
 
@@ -27,7 +25,7 @@ public enum GTCombType {
     // Gem
     STONE("stone", 0x808080, 0x999999),
     CERTUS("certus", 0x57CFFB, 0xBBEEFF),
-    FLUIX("fluix", 0xA375FF, 0xB591FF, Loader.isModLoaded(GTValues.MODID_APPENG)),
+    FLUIX("fluix", 0xA375FF, 0xB591FF, Mods.AppliedEnergistics2.isModLoaded()),
     REDSTONE("redstone", 0x7D0F0F, 0xD11919),
     RAREEARTH("rareearth", 0x555643, 0x343428),
     LAPIS("lapis", 0x1947D1, 0x476CDA),
@@ -38,7 +36,7 @@ public enum GTCombType {
     EMERALD("emerald", 0x248F24, 0x2EB82E),
     PYROPE("pyrope", 0x763162, 0x8B8B8B),
     GROSSULAR("grossular", 0x9B4E00, 0x8B8B8B),
-    SPARKLING("sparkling", 0x7A007A, 0xFFFFFF, Loader.isModLoaded(GTValues.MODID_MB)),
+    SPARKLING("sparkling", 0x7A007A, 0xFFFFFF, Mods.MagicBees.isModLoaded()),
 
     // Metal
     SLAG("slag", 0xD4D4D4, 0x58300B),
diff --git a/src/main/java/gregtech/integration/forestry/bees/GTDropItem.java b/src/main/java/gregtech/integration/forestry/bees/GTDropItem.java
index 45a67068b2a..a8453a6a151 100644
--- a/src/main/java/gregtech/integration/forestry/bees/GTDropItem.java
+++ b/src/main/java/gregtech/integration/forestry/bees/GTDropItem.java
@@ -1,6 +1,7 @@
 package gregtech.integration.forestry.bees;
 
 import gregtech.api.GTValues;
+import gregtech.api.util.Mods;
 
 import net.minecraft.creativetab.CreativeTabs;
 import net.minecraft.item.Item;
@@ -44,7 +45,7 @@ private void setResearchSuitability(@Nullable ISpeciesRoot speciesRoot) {
     public void registerModel(@NotNull Item item, @NotNull IModelManager manager) {
         manager.registerItemModel(item, 0);
         for (int i = 0; i < GTDropType.VALUES.length; i++) {
-            manager.registerItemModel(item, i, GTValues.MODID_FR, "gt.honey_drop");
+            manager.registerItemModel(item, i, Mods.Names.FORESTRY, "gt.honey_drop");
         }
     }
 
diff --git a/src/main/java/gregtech/integration/forestry/frames/GTItemFrame.java b/src/main/java/gregtech/integration/forestry/frames/GTItemFrame.java
index 088c5ed7e73..e8f9a46d45e 100644
--- a/src/main/java/gregtech/integration/forestry/frames/GTItemFrame.java
+++ b/src/main/java/gregtech/integration/forestry/frames/GTItemFrame.java
@@ -1,6 +1,7 @@
 package gregtech.integration.forestry.frames;
 
 import gregtech.api.GTValues;
+import gregtech.api.util.Mods;
 
 import net.minecraft.client.util.ITooltipFlag;
 import net.minecraft.item.Item;
@@ -62,7 +63,7 @@ public IBeeModifier getBeeModifier() {
     @SuppressWarnings("deprecation")
     @Override
     public void registerModel(@NotNull Item item, @NotNull IModelManager manager) {
-        manager.registerItemModel(item, 0, GTValues.MODID_FR, "gt.frame_" + type.getName().toLowerCase());
+        manager.registerItemModel(item, 0, Mods.Names.FORESTRY, "gt.frame_" + type.getName().toLowerCase());
     }
 
     public ItemStack getItemStack() {
diff --git a/src/main/java/gregtech/integration/forestry/recipes/CombRecipes.java b/src/main/java/gregtech/integration/forestry/recipes/CombRecipes.java
index 66df87dc408..a7661982176 100644
--- a/src/main/java/gregtech/integration/forestry/recipes/CombRecipes.java
+++ b/src/main/java/gregtech/integration/forestry/recipes/CombRecipes.java
@@ -12,8 +12,8 @@
 import gregtech.api.unification.material.properties.PropertyKey;
 import gregtech.api.unification.ore.OrePrefix;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.Mods;
 import gregtech.common.items.MetaItems;
-import gregtech.integration.IntegrationUtil;
 import gregtech.integration.forestry.ForestryUtil;
 import gregtech.integration.forestry.bees.GTCombItem;
 import gregtech.integration.forestry.bees.GTCombType;
@@ -21,7 +21,6 @@
 
 import net.minecraft.item.ItemStack;
 import net.minecraftforge.fluids.FluidStack;
-import net.minecraftforge.fml.common.Loader;
 
 import appeng.core.Api;
 import com.google.common.collect.ImmutableMap;
@@ -98,8 +97,8 @@ public static void initGTCombs() {
                         ModuleCore.getItems().refractoryWax.getItemStack() },
                 new int[] { 20 * 100, 50 * 100 }, Voltage.HV, 196);
         ItemStack wax = ModuleCore.getItems().beeswax.getItemStack();
-        if (Loader.isModLoaded(GTValues.MODID_MB)) {
-            wax = IntegrationUtil.getModItem(GTValues.MODID_MB, "wax", 2);
+        if (Mods.MagicBees.isModLoaded()) {
+            wax = Mods.MagicBees.getItem("wax", 2);
         }
         addCentrifugeToItemStack(GTCombType.LAPOTRON,
                 new ItemStack[] { OreDictUnifier.get(OrePrefix.dust, Materials.Lapotron), wax },
@@ -271,11 +270,10 @@ public static void initGTCombs() {
                 .cleanroom(CleanroomType.CLEANROOM)
                 .duration(3000).EUt(Voltage.UV.getChemicalEnergy()).buildAndRegister();
 
-        if (Loader.isModLoaded(GTValues.MODID_MB)) {
+        if (Mods.MagicBees.isModLoaded()) {
             addProcessGT(GTCombType.SPARKLING, new Material[] { Materials.NetherStar }, Voltage.EV);
             addCentrifugeToItemStack(GTCombType.SPARKLING,
-                    new ItemStack[] { IntegrationUtil.getModItem(GTValues.MODID_MB, "wax", 0),
-                            IntegrationUtil.getModItem(GTValues.MODID_MB, "resource", 5),
+                    new ItemStack[] { Mods.MagicBees.getItem("wax", 0), Mods.MagicBees.getItem("resource", 5),
                             OreDictUnifier.get(OrePrefix.dustTiny, Materials.NetherStar) },
                     new int[] { 50 * 100, 10 * 100, 10 * 100 }, Voltage.EV);
         }
@@ -290,7 +288,7 @@ public static void initGTCombs() {
         addExtractorProcess(GTCombType.HYDROGEN, Materials.Hydrogen.getFluid(500), Voltage.MV, 100);
         addExtractorProcess(GTCombType.FLUORINE, Materials.Fluorine.getFluid(250), Voltage.MV, 128);
 
-        if (Loader.isModLoaded(GTValues.MODID_APPENG)) {
+        if (Mods.AppliedEnergistics2.isModLoaded()) {
             ItemStack fluixDust = OreDictUnifier.get("dustFluix");
             if (fluixDust == ItemStack.EMPTY) {
                 fluixDust = Api.INSTANCE.definitions().materials().fluixDust().maybeStack(1).orElse(ItemStack.EMPTY);
diff --git a/src/main/java/gregtech/integration/forestry/recipes/ForestryElectrodeRecipes.java b/src/main/java/gregtech/integration/forestry/recipes/ForestryElectrodeRecipes.java
index 171f575bc32..72014ca62c6 100644
--- a/src/main/java/gregtech/integration/forestry/recipes/ForestryElectrodeRecipes.java
+++ b/src/main/java/gregtech/integration/forestry/recipes/ForestryElectrodeRecipes.java
@@ -1,14 +1,13 @@
 package gregtech.integration.forestry.recipes;
 
-import gregtech.api.GTValues;
 import gregtech.api.unification.stack.UnificationEntry;
+import gregtech.api.util.Mods;
 import gregtech.integration.forestry.ForestryModule;
 
 import net.minecraft.init.Blocks;
 import net.minecraft.init.Items;
 import net.minecraft.item.ItemStack;
 import net.minecraftforge.fluids.FluidStack;
-import net.minecraftforge.fml.common.Loader;
 
 import forestry.api.recipes.RecipeManagers;
 import forestry.core.ModuleCore;
@@ -137,7 +136,7 @@ public static void addGregTechMachineRecipes() {
                 .output(ForestryModule.ELECTRODE_GOLD, 2)
                 .buildAndRegister();
 
-        if (Loader.isModLoaded(GTValues.MODID_IC2) || Loader.isModLoaded(GTValues.MODID_BINNIE)) {
+        if (Mods.IndustrialCraft2.isModLoaded() || Mods.BinnieCore.isModLoaded()) {
             CIRCUIT_ASSEMBLER_RECIPES.recipeBuilder().duration(150).EUt(16)
                     .input(ForestryModule.ELECTRODE_IRON)
                     .fluidInputs(Glass.getFluid(100))
@@ -176,7 +175,7 @@ public static void addGregTechMachineRecipes() {
                 .output(ForestryModule.ELECTRODE_OBSIDIAN, 4)
                 .buildAndRegister();
 
-        if (Loader.isModLoaded(GTValues.MODID_XU2)) {
+        if (Mods.ExtraUtilities2.isModLoaded()) {
             CIRCUIT_ASSEMBLER_RECIPES.recipeBuilder().duration(150).EUt(16)
                     .input(ForestryModule.ELECTRODE_ORCHID)
                     .fluidInputs(Glass.getFluid(100))
@@ -191,8 +190,7 @@ public static void addGregTechMachineRecipes() {
         }
 
         // todo mixin forestry to allow this tube always, since we have rubber (once mixin port is done)
-        if (Loader.isModLoaded(GTValues.MODID_IC2) || Loader.isModLoaded(GTValues.MODID_TR) ||
-                Loader.isModLoaded(GTValues.MODID_BINNIE)) {
+        if (Mods.IndustrialCraft2.isModLoaded() || Mods.TechReborn.isModLoaded() || Mods.BinnieCore.isModLoaded()) {
             CIRCUIT_ASSEMBLER_RECIPES.recipeBuilder().duration(150).EUt(16)
                     .input(ForestryModule.ELECTRODE_RUBBER)
                     .fluidInputs(Glass.getFluid(100))
@@ -248,7 +246,7 @@ private static void addForestryMachineRecipes() {
                 '#', new UnificationEntry(wireGtSingle, RedAlloy).toString(),
                 'X', new UnificationEntry(plate, Bronze).toString());
 
-        if (Loader.isModLoaded(GTValues.MODID_IC2) || Loader.isModLoaded(GTValues.MODID_BINNIE)) {
+        if (Mods.IndustrialCraft2.isModLoaded() || Mods.BinnieCore.isModLoaded()) {
             addFabricatorRecipe(EnumElectronTube.IRON,
                     "SXS", "#X#", "XXX",
                     'S', new UnificationEntry(screw, Iron).toString(),
@@ -301,7 +299,7 @@ private static void addForestryMachineRecipes() {
                 '#', new UnificationEntry(plate, EnderEye).toString(),
                 'X', new ItemStack(Blocks.END_STONE));
 
-        if (Loader.isModLoaded(GTValues.MODID_XU2)) {
+        if (Mods.ExtraUtilities2.isModLoaded()) {
             addFabricatorRecipe(EnumElectronTube.ORCHID,
                     " X ", "#X#", "XXX",
                     '#', new ItemStack(Items.REPEATER),
diff --git a/src/main/java/gregtech/integration/forestry/recipes/ForestryExtractorRecipes.java b/src/main/java/gregtech/integration/forestry/recipes/ForestryExtractorRecipes.java
index 5a57da584cc..e1face8fe9e 100644
--- a/src/main/java/gregtech/integration/forestry/recipes/ForestryExtractorRecipes.java
+++ b/src/main/java/gregtech/integration/forestry/recipes/ForestryExtractorRecipes.java
@@ -1,16 +1,14 @@
 package gregtech.integration.forestry.recipes;
 
-import gregtech.api.GTValues;
 import gregtech.api.recipes.RecipeBuilder;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.GTUtility;
-import gregtech.integration.IntegrationUtil;
+import gregtech.api.util.Mods;
 
 import net.minecraft.init.Items;
 import net.minecraft.item.ItemStack;
 import net.minecraftforge.fluids.FluidStack;
-import net.minecraftforge.fml.common.Loader;
 
 import forestry.core.fluids.Fluids;
 
@@ -18,171 +16,107 @@ public class ForestryExtractorRecipes {
 
     public static void init() {
         // Commonly used items
-        ItemStack mulch = IntegrationUtil.getModItem(GTValues.MODID_FR, "mulch", 0);
-        ItemStack propolis = IntegrationUtil.getModItem(GTValues.MODID_FR, "propolis", 0);
+        ItemStack mulch = Mods.Forestry.getItem("mulch");
+        ItemStack propolis = Mods.Forestry.getItem("propolis");
 
         // Vanilla Fruit Juice items
         addFruitJuiceRecipe(new ItemStack(Items.CARROT), 200, GTUtility.copy(mulch), 2000);
         addFruitJuiceRecipe(new ItemStack(Items.APPLE), 200, GTUtility.copy(mulch), 2000);
 
         // Forestry fruits
-        addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_FR, "fruits", 0), 50, GTUtility.copy(mulch), 500);
-        addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_FR, "fruits", 1), 180, GTUtility.copy(mulch), 500);
-        addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_FR, "fruits", 2), 220, GTUtility.copy(mulch), 200);
-        addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_FR, "fruits", 3), 400, GTUtility.copy(mulch),
-                1000);
-        addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_FR, "fruits", 4), 100, GTUtility.copy(mulch),
-                6000);
-        addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_FR, "fruits", 5), 50, GTUtility.copy(mulch),
-                2000);
-        addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_FR, "fruits", 6), 600, GTUtility.copy(mulch),
-                1000);
+        addSeedOilRecipe(Mods.Forestry.getItem("fruits", 0), 50, GTUtility.copy(mulch), 500);
+        addSeedOilRecipe(Mods.Forestry.getItem("fruits", 1), 180, GTUtility.copy(mulch), 500);
+        addSeedOilRecipe(Mods.Forestry.getItem("fruits", 2), 220, GTUtility.copy(mulch), 200);
+        addFruitJuiceRecipe(Mods.Forestry.getItem("fruits", 3), 400, GTUtility.copy(mulch), 1000);
+        addFruitJuiceRecipe(Mods.Forestry.getItem("fruits", 4), 100, GTUtility.copy(mulch), 6000);
+        addFruitJuiceRecipe(Mods.Forestry.getItem("fruits", 5), 50, GTUtility.copy(mulch), 2000);
+        addFruitJuiceRecipe(Mods.Forestry.getItem("fruits", 6), 600, GTUtility.copy(mulch), 1000);
 
         // Honey, Honeydew
-        addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_FR, "honey_drop", 0), 100, GTUtility.copy(propolis),
-                0);
-        addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_FR, "honeydew", 0), 100);
+        addHoneyRecipe(Mods.Forestry.getItem("honey_drop"), 100, GTUtility.copy(propolis), 0);
+        addHoneyRecipe(Mods.Forestry.getItem("honeydew"), 100);
 
-        if (Loader.isModLoaded(GTValues.MODID_EB)) {
+        if (Mods.ExtraBees.isModLoaded()) {
             // Propolis
-            addExtractorRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "propolis", 0),
-                    Materials.Water.getFluid(500));
-            addExtractorRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "propolis", 1),
-                    Materials.Oil.getFluid(500));
-            addExtractorRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "propolis", 2),
-                    Materials.Creosote.getFluid(500));
+            addExtractorRecipe(Mods.ExtraBees.getItem("propolis", 0), Materials.Water.getFluid(500));
+            addExtractorRecipe(Mods.ExtraBees.getItem("propolis", 1), Materials.Oil.getFluid(500));
+            addExtractorRecipe(Mods.ExtraBees.getItem("propolis", 2), Materials.Creosote.getFluid(500));
 
             // Drops
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 3), 200);
-            addExtractorRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 6),
-                    Fluids.MILK.getFluid(200));
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 13), 200,
-                    IntegrationUtil.getModItem(GTValues.MODID_EB, "misc", 19), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 14), 200,
-                    IntegrationUtil.getModItem(GTValues.MODID_EB, "misc", 20), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 15), 200,
-                    IntegrationUtil.getModItem(GTValues.MODID_EB, "misc", 21), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 16), 200,
-                    IntegrationUtil.getModItem(GTValues.MODID_EB, "misc", 22), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 17), 200,
-                    IntegrationUtil.getModItem(GTValues.MODID_EB, "misc", 24), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 18), 200,
-                    IntegrationUtil.getModItem(GTValues.MODID_EB, "misc", 23), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 19), 200,
-                    IntegrationUtil.getModItem(GTValues.MODID_EB, "misc", 25), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 20), 200,
-                    new ItemStack(Items.DYE, 1, 14), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 21), 200,
-                    new ItemStack(Items.DYE, 1, 6), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 22), 200,
-                    new ItemStack(Items.DYE, 1, 5), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 23), 200,
-                    new ItemStack(Items.DYE, 1, 8), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 24), 200,
-                    new ItemStack(Items.DYE, 1, 12), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 25), 200,
-                    new ItemStack(Items.DYE, 1, 9), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 26), 200,
-                    new ItemStack(Items.DYE, 1, 10), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 27), 200,
-                    new ItemStack(Items.DYE, 1, 13), 0);
-            addHoneyRecipe(IntegrationUtil.getModItem(GTValues.MODID_EB, "honey_drop", 28), 200,
-                    new ItemStack(Items.DYE, 1, 7), 0);
+            addFruitJuiceRecipe(Mods.ExtraBees.getItem("honey_drop", 3), 200);
+            addExtractorRecipe(Mods.ExtraBees.getItem("honey_drop", 6), Fluids.MILK.getFluid(200));
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 13), 200, Mods.ExtraBees.getItem("misc", 19), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 14), 200, Mods.ExtraBees.getItem("misc", 20), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 15), 200, Mods.ExtraBees.getItem("misc", 21), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 16), 200, Mods.ExtraBees.getItem("misc", 22), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 17), 200, Mods.ExtraBees.getItem("misc", 24), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 18), 200, Mods.ExtraBees.getItem("misc", 23), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 19), 200, Mods.ExtraBees.getItem("misc", 25), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 20), 200, new ItemStack(Items.DYE, 1, 14), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 21), 200, new ItemStack(Items.DYE, 1, 6), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 22), 200, new ItemStack(Items.DYE, 1, 5), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 23), 200, new ItemStack(Items.DYE, 1, 8), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 24), 200, new ItemStack(Items.DYE, 1, 12), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 25), 200, new ItemStack(Items.DYE, 1, 9), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 26), 200, new ItemStack(Items.DYE, 1, 10), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 27), 200, new ItemStack(Items.DYE, 1, 13), 0);
+            addHoneyRecipe(Mods.ExtraBees.getItem("honey_drop", 28), 200, new ItemStack(Items.DYE, 1, 7), 0);
         }
 
-        if (Loader.isModLoaded(GTValues.MODID_ET)) {
+        if (Mods.ExtraTrees.isModLoaded()) {
 
             // Fruits
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 0), 150, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 1), 400, GTUtility.copy(mulch),
-                    1500);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 2), 300, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 3), 300, GTUtility.copy(mulch),
-                    1000);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 4), 50, GTUtility.copy(mulch), 500);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 5), 50, GTUtility.copy(mulch), 300);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 6), 50, GTUtility.copy(mulch), 500);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 7), 50, GTUtility.copy(mulch),
-                    500);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 8), 100, GTUtility.copy(mulch),
-                    6000);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 9), 80, GTUtility.copy(mulch), 500);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 10), 150, GTUtility.copy(mulch),
-                    4000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 11), 500, GTUtility.copy(mulch),
-                    1500);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 12), 150, GTUtility.copy(mulch),
-                    4000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 13), 300, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 14), 400, GTUtility.copy(mulch),
-                    1500);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 15), 400, GTUtility.copy(mulch),
-                    1500);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 16), 300, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 17), 300, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 18), 400, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 19), 150, GTUtility.copy(mulch),
-                    4000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 20), 300, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 21), 300, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 22), 300, GTUtility.copy(mulch),
-                    2000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 23), 200, GTUtility.copy(mulch),
-                    1000);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 24), 150, GTUtility.copy(mulch),
-                    500);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 25), 180, GTUtility.copy(mulch),
-                    500);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 26), 100, GTUtility.copy(mulch),
-                    400);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 27), 50, GTUtility.copy(mulch), 200);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 28), 100, GTUtility.copy(mulch),
-                    3000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 29), 100, GTUtility.copy(mulch),
-                    3000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 30), 100, GTUtility.copy(mulch),
-                    4000);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 31), 20, GTUtility.copy(mulch), 200);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 32), 50, GTUtility.copy(mulch), 300);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 33), 50, GTUtility.copy(mulch), 300);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 34), 100, GTUtility.copy(mulch),
-                    500);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 35), 50, GTUtility.copy(mulch), 300);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 36), 50, GTUtility.copy(mulch), 500);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 37), 20, GTUtility.copy(mulch), 200);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 38), 300, GTUtility.copy(mulch),
-                    1500);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 39), 25, GTUtility.copy(mulch), 200);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 46), 50, GTUtility.copy(mulch),
-                    500);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 50), 300, GTUtility.copy(mulch),
-                    2500);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 51), 150, GTUtility.copy(mulch),
-                    1500);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 52), 300, GTUtility.copy(mulch),
-                    1500);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 53), 50, GTUtility.copy(mulch),
-                    1000);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 54), 50, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 55), 100, GTUtility.copy(mulch),
-                    1000);
-            addSeedOilRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 56), 100, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 57), 400, GTUtility.copy(mulch),
-                    2000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 58), 300, GTUtility.copy(mulch),
-                    1000);
-            addFruitJuiceRecipe(IntegrationUtil.getModItem(GTValues.MODID_ET, "food", 59), 50, GTUtility.copy(mulch),
-                    1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 0), 150, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 1), 400, GTUtility.copy(mulch), 1500);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 2), 300, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 3), 300, GTUtility.copy(mulch), 1000);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 4), 50, GTUtility.copy(mulch), 500);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 5), 50, GTUtility.copy(mulch), 300);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 6), 50, GTUtility.copy(mulch), 500);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 7), 50, GTUtility.copy(mulch), 500);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 8), 100, GTUtility.copy(mulch), 6000);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 9), 80, GTUtility.copy(mulch), 500);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 10), 150, GTUtility.copy(mulch), 4000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 11), 500, GTUtility.copy(mulch), 1500);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 12), 150, GTUtility.copy(mulch), 4000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 13), 300, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 14), 400, GTUtility.copy(mulch), 1500);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 15), 400, GTUtility.copy(mulch), 1500);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 16), 300, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 17), 300, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 18), 400, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 19), 150, GTUtility.copy(mulch), 4000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 20), 300, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 21), 300, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 22), 300, GTUtility.copy(mulch), 2000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 23), 200, GTUtility.copy(mulch), 1000);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 24), 150, GTUtility.copy(mulch), 500);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 25), 180, GTUtility.copy(mulch), 500);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 26), 100, GTUtility.copy(mulch), 400);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 27), 50, GTUtility.copy(mulch), 200);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 28), 100, GTUtility.copy(mulch), 3000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 29), 100, GTUtility.copy(mulch), 3000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 30), 100, GTUtility.copy(mulch), 4000);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 31), 20, GTUtility.copy(mulch), 200);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 32), 50, GTUtility.copy(mulch), 300);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 33), 50, GTUtility.copy(mulch), 300);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 34), 100, GTUtility.copy(mulch), 500);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 35), 50, GTUtility.copy(mulch), 300);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 36), 50, GTUtility.copy(mulch), 500);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 37), 20, GTUtility.copy(mulch), 200);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 38), 300, GTUtility.copy(mulch), 1500);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 39), 25, GTUtility.copy(mulch), 200);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 46), 50, GTUtility.copy(mulch), 500);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 50), 300, GTUtility.copy(mulch), 2500);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 51), 150, GTUtility.copy(mulch), 1500);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 52), 300, GTUtility.copy(mulch), 1500);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 53), 50, GTUtility.copy(mulch), 1000);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 54), 50, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 55), 100, GTUtility.copy(mulch), 1000);
+            addSeedOilRecipe(Mods.ExtraTrees.getItem("food", 56), 100, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 57), 400, GTUtility.copy(mulch), 2000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 58), 300, GTUtility.copy(mulch), 1000);
+            addFruitJuiceRecipe(Mods.ExtraTrees.getItem("food", 59), 50, GTUtility.copy(mulch), 1000);
         }
     }
 
diff --git a/src/main/java/gregtech/integration/forestry/recipes/ForestryMiscRecipes.java b/src/main/java/gregtech/integration/forestry/recipes/ForestryMiscRecipes.java
index 5f4f214c294..85239b0a1a5 100644
--- a/src/main/java/gregtech/integration/forestry/recipes/ForestryMiscRecipes.java
+++ b/src/main/java/gregtech/integration/forestry/recipes/ForestryMiscRecipes.java
@@ -6,8 +6,8 @@
 import gregtech.api.unification.material.Materials;
 import gregtech.api.unification.ore.OrePrefix;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.Mods;
 import gregtech.common.items.MetaItems;
-import gregtech.integration.IntegrationUtil;
 import gregtech.integration.forestry.ForestryConfig;
 import gregtech.integration.forestry.ForestryUtil;
 import gregtech.integration.forestry.bees.GTDropType;
@@ -15,7 +15,6 @@
 import net.minecraft.init.Blocks;
 import net.minecraft.init.Items;
 import net.minecraft.item.ItemStack;
-import net.minecraftforge.fml.common.Loader;
 
 import forestry.api.recipes.RecipeManagers;
 import forestry.apiculture.ModuleApiculture;
@@ -28,7 +27,7 @@
 public class ForestryMiscRecipes {
 
     public static void init() {
-        if (ForestryConfig.enableGTBees) {
+        if (ForestryConfig.enableGTBees && Mods.ForestryApiculture.isModLoaded()) {
             // Oil Drop
             ItemStack dropStack = ForestryUtil.getDropStack(GTDropType.OIL);
             RecipeMaps.EXTRACTOR_RECIPES.recipeBuilder()
@@ -45,8 +44,8 @@ public static void init() {
             // Biomass Drop
             dropStack = ForestryUtil.getDropStack(GTDropType.BIOMASS);
             ItemStack propolisStack = ModuleApiculture.getItems().propolis.get(EnumPropolis.NORMAL, 1);
-            if (Loader.isModLoaded(GTValues.MODID_EB)) {
-                propolisStack = IntegrationUtil.getModItem(GTValues.MODID_EB, "propolis", 7);
+            if (Mods.ExtraBees.isModLoaded()) {
+                propolisStack = Mods.ExtraBees.getItem("propolis", 7);
             }
             RecipeMaps.EXTRACTOR_RECIPES.recipeBuilder()
                     .inputs(dropStack)
@@ -202,7 +201,7 @@ public static void init() {
         }
 
         // Fertilizer
-        ItemStack fertilizer = IntegrationUtil.getModItem(GTValues.MODID_FR, "fertilizer_compound", 0);
+        ItemStack fertilizer = Mods.Forestry.getItem("fertilizer_compound");
         RecipeMaps.MIXER_RECIPES.recipeBuilder()
                 .input("sand", 2)
                 .input(OrePrefix.dust, Materials.Apatite)
@@ -224,8 +223,8 @@ public static void init() {
                 .outputs(GTUtility.copy(30, fertilizer))
                 .duration(100).EUt(16).buildAndRegister();
 
-        if (Loader.isModLoaded(GTValues.MODID_MB)) {
-            ItemStack concentratedCompound = IntegrationUtil.getModItem(GTValues.MODID_MB, "resource", 2);
+        if (Mods.MagicBees.isModLoaded()) {
+            ItemStack concentratedCompound = Mods.MagicBees.getItem("resource", 2);
             RecipeMaps.MIXER_RECIPES.recipeBuilder()
                     .input("sand", 2)
                     .inputs(GTUtility.copy(concentratedCompound))
@@ -249,7 +248,7 @@ public static void init() {
         }
 
         // Compost
-        ItemStack compost = IntegrationUtil.getModItem(GTValues.MODID_FR, "fertilizer_bio", 0);
+        ItemStack compost = Mods.Forestry.getItem("fertilizer_bio");
         RecipeMaps.MIXER_RECIPES.recipeBuilder()
                 .input(Blocks.DIRT, 1, true)
                 .input(Items.WHEAT, 4)
@@ -279,19 +278,19 @@ public static void init() {
 
         // Phosphor
         RecipeMaps.EXTRACTOR_RECIPES.recipeBuilder()
-                .inputs(IntegrationUtil.getModItem(GTValues.MODID_FR, "phosphor", 0))
+                .inputs(Mods.Forestry.getItem("phosphor"))
                 .chancedOutput(OrePrefix.dust, Materials.Phosphorus, 1000, 0)
                 .fluidOutputs(Materials.Lava.getFluid(800))
                 .duration(256).EUt(GTValues.VA[GTValues.MV]).buildAndRegister();
 
         // Ice Shard
         RecipeMaps.MACERATOR_RECIPES.recipeBuilder()
-                .inputs(IntegrationUtil.getModItem(GTValues.MODID_FR, "crafting_material", 5))
+                .inputs(Mods.Forestry.getItem("crafting_material", 5))
                 .output(OrePrefix.dust, Materials.Ice)
                 .duration(16).EUt(4).buildAndRegister();
 
         // Mulch
-        ItemStack mulch = IntegrationUtil.getModItem(GTValues.MODID_FR, "mulch", 0);
+        ItemStack mulch = Mods.Forestry.getItem("mulch");
         RecipeMaps.CHEMICAL_BATH_RECIPES.recipeBuilder()
                 .input(MetaItems.BIO_CHAFF)
                 .fluidInputs(Materials.Water.getFluid(750))
@@ -300,9 +299,9 @@ public static void init() {
                 .chancedOutput(GTUtility.copy(4, mulch), 2000, 0)
                 .duration(500).EUt(GTValues.VA[GTValues.LV]).buildAndRegister();
 
-        if (Loader.isModLoaded(GTValues.MODID_ET)) {
+        if (Mods.ExtraTrees.isModLoaded()) {
             RecipeMaps.MIXER_RECIPES.recipeBuilder()
-                    .inputs(IntegrationUtil.getModItem(GTValues.MODID_ET, "misc", 1))
+                    .inputs(Mods.ExtraTrees.getItem("misc", 1))
                     .fluidInputs(Materials.Water.getFluid(500))
                     .outputs(GTUtility.copy(mulch))
                     .duration(600).EUt(2).buildAndRegister();
@@ -328,7 +327,7 @@ public static void init() {
                 .duration(900).EUt(10).buildAndRegister();
 
         // Humus
-        ItemStack humus = IntegrationUtil.getModItem(GTValues.MODID_FR, "humus", 0);
+        ItemStack humus = Mods.Forestry.getItem("humus");
         RecipeMaps.MIXER_RECIPES.recipeBuilder()
                 .inputs(GTUtility.copy(2, mulch))
                 .input(Blocks.DIRT, 2, true)
@@ -362,35 +361,35 @@ public static void init() {
                 .input(OrePrefix.plate, Materials.Tin, 2)
                 .input(Blocks.GLASS_PANE)
                 .circuitMeta(1)
-                .outputs(IntegrationUtil.getModItem(GTValues.MODID_FR, "can", 0))
+                .outputs(Mods.Forestry.getItem("can"))
                 .duration(120).EUt(7).buildAndRegister();
 
         RecipeMaps.EXTRUDER_RECIPES.recipeBuilder()
-                .inputs(IntegrationUtil.getModItem(GTValues.MODID_FR, "beeswax", 0))
+                .inputs(Mods.Forestry.getItem("beeswax"))
                 .notConsumable(MetaItems.SHAPE_EXTRUDER_CELL)
-                .outputs(IntegrationUtil.getModItem(GTValues.MODID_FR, "capsule", 0))
+                .outputs(Mods.Forestry.getItem("capsule"))
                 .duration(64).EUt(16).buildAndRegister();
 
         RecipeMaps.EXTRUDER_RECIPES.recipeBuilder()
-                .inputs(IntegrationUtil.getModItem(GTValues.MODID_FR, "refractory_wax", 0))
+                .inputs(Mods.Forestry.getItem("refractory_wax"))
                 .notConsumable(MetaItems.SHAPE_EXTRUDER_CELL)
-                .outputs(IntegrationUtil.getModItem(GTValues.MODID_FR, "refractory", 0))
+                .outputs(Mods.Forestry.getItem("refractory"))
                 .duration(128).EUt(16).buildAndRegister();
 
         // Propolis
         RecipeMaps.CENTRIFUGE_RECIPES.recipeBuilder()
-                .inputs(IntegrationUtil.getModItem(GTValues.MODID_FR, "propolis", 0))
+                .inputs(Mods.Forestry.getItem("propolis"))
                 .output(MetaItems.STICKY_RESIN)
                 .duration(128).EUt(5).buildAndRegister();
 
-        if (Loader.isModLoaded(GTValues.MODID_GENETICS)) {
+        if (Mods.Genetics.isModLoaded()) {
 
             // DNA Dye
             RecipeMaps.MIXER_RECIPES.recipeBuilder()
                     .input(OrePrefix.dust, Materials.Glowstone)
                     .input("dyePurple")
                     .input("dyeBlue")
-                    .outputs(IntegrationUtil.getModItem(GTValues.MODID_GENETICS, "misc", 1, 8))
+                    .outputs(Mods.Genetics.getItem("misc", 1, 8))
                     .duration(100).EUt(GTValues.VA[GTValues.LV]).buildAndRegister();
 
             // Fluorescent Dye
@@ -398,14 +397,14 @@ public static void init() {
                     .input(OrePrefix.dust, Materials.Glowstone)
                     .input("dyeOrange")
                     .input("dyeYellow")
-                    .outputs(IntegrationUtil.getModItem(GTValues.MODID_GENETICS, "misc", 2, 2))
+                    .outputs(Mods.Genetics.getItem("misc", 2, 2))
                     .duration(100).EUt(GTValues.VA[GTValues.LV]).buildAndRegister();
 
             // Growth Medium
             RecipeMaps.MIXER_RECIPES.recipeBuilder()
                     .input(OrePrefix.dust, Materials.Sugar)
                     .input(OrePrefix.dust, Materials.Bone)
-                    .outputs(IntegrationUtil.getModItem(GTValues.MODID_GENETICS, "misc", 4, 2))
+                    .outputs(Mods.Genetics.getItem("misc", 4, 2))
                     .duration(400).EUt(16).buildAndRegister();
         }
 
@@ -413,7 +412,7 @@ public static void init() {
         RecipeMaps.FLUID_SOLIDFICATION_RECIPES.recipeBuilder()
                 .notConsumable(MetaItems.SHAPE_MOLD_NUGGET)
                 .fluidInputs(Fluids.FOR_HONEY.getFluid(200))
-                .outputs(IntegrationUtil.getModItem(GTValues.MODID_FR, "honey_drop", 0))
+                .outputs(Mods.Forestry.getItem("honey_drop"))
                 .duration(400).EUt(7).buildAndRegister();
 
         RecipeMaps.CENTRIFUGE_RECIPES.recipeBuilder()
@@ -425,7 +424,7 @@ public static void init() {
     public static void initRemoval() {
         ModHandler.removeRecipeByName("forestry:sand_to_fertilizer");
         ModHandler.removeRecipeByName("forestry:ash_to_fertilizer");
-        if (Loader.isModLoaded(GTValues.MODID_MB)) {
+        if (Mods.MagicBees.isModLoaded()) {
             ModHandler.removeRecipeByName("magicbees:fertilizer1");
             ModHandler.removeRecipeByName("magicbees:fertilizer2");
             ModHandler.removeRecipeByName("magicbees:fertilizer3");
@@ -437,7 +436,7 @@ public static void initRemoval() {
         ModHandler.removeRecipeByName("forestry:tin_can");
         ModHandler.removeRecipeByName("forestry:wax_capsule");
         ModHandler.removeRecipeByName("forestry:refractory_capsule");
-        if (Loader.isModLoaded(GTValues.MODID_GENETICS)) {
+        if (Mods.Genetics.isModLoaded()) {
             ModHandler.removeRecipeByName("genetics:dna_dye_from_glowstone");
             ModHandler.removeRecipeByName("genetics:dna_dye");
             ModHandler.removeRecipeByName("genetics:fluorescent_dye");
diff --git a/src/main/java/gregtech/integration/forestry/tools/ScoopBehavior.java b/src/main/java/gregtech/integration/forestry/tools/ScoopBehavior.java
index 221fadaa6f6..6eed93239d8 100644
--- a/src/main/java/gregtech/integration/forestry/tools/ScoopBehavior.java
+++ b/src/main/java/gregtech/integration/forestry/tools/ScoopBehavior.java
@@ -1,8 +1,8 @@
 package gregtech.integration.forestry.tools;
 
-import gregtech.api.GTValues;
 import gregtech.api.items.toolitem.ToolHelper;
 import gregtech.api.items.toolitem.behavior.IToolBehavior;
+import gregtech.api.util.Mods;
 
 import net.minecraft.client.resources.I18n;
 import net.minecraft.client.util.ITooltipFlag;
@@ -13,7 +13,6 @@
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.world.World;
 import net.minecraftforge.event.ForgeEventFactory;
-import net.minecraftforge.fml.common.Loader;
 
 import forestry.api.lepidopterology.EnumFlutterType;
 import forestry.api.lepidopterology.IAlleleButterflySpecies;
@@ -32,7 +31,7 @@ private ScoopBehavior() {/**/}
     @Override
     public void hitEntity(@NotNull ItemStack stack, @NotNull EntityLivingBase target,
                           @NotNull EntityLivingBase attacker) {
-        if (!Loader.isModLoaded(GTValues.MODID_FR)) return;
+        if (!Mods.Forestry.isModLoaded()) return;
         if (!(target instanceof IEntityButterfly butterfly)) return;
         if (!(attacker instanceof EntityPlayer player)) return;
         if (attacker.world == null || attacker.world.isRemote) return;
diff --git a/src/main/java/gregtech/integration/groovy/GroovyExpansions.java b/src/main/java/gregtech/integration/groovy/GroovyExpansions.java
new file mode 100644
index 00000000000..8c23fdf270f
--- /dev/null
+++ b/src/main/java/gregtech/integration/groovy/GroovyExpansions.java
@@ -0,0 +1,42 @@
+package gregtech.integration.groovy;
+
+import gregtech.api.recipes.RecipeBuilder;
+import gregtech.api.unification.material.Material;
+import gregtech.api.unification.material.event.MaterialEvent;
+
+import net.minecraft.util.ResourceLocation;
+
+import com.cleanroommc.groovyscript.GroovyScript;
+import com.cleanroommc.groovyscript.api.GroovyLog;
+
+public class GroovyExpansions {
+
+    public static > RecipeBuilder property(RecipeBuilder builder, String key,
+                                                                         Object value) {
+        if (!builder.applyProperty(key, value)) {
+            GroovyLog.get().error("Failed to add property '{}' with '{}' to recipe", key, value);
+        }
+        return builder;
+    }
+
+    public static Material.Builder materialBuilder(MaterialEvent event, int id, ResourceLocation resourceLocation) {
+        return new Material.Builder(id, resourceLocation);
+    }
+
+    public static Material.Builder materialBuilder(MaterialEvent event, int id, String domain, String path) {
+        return materialBuilder(event, id, domain, path);
+    }
+
+    public static Material.Builder materialBuilder(MaterialEvent event, int id, String s) {
+        String domain, path;
+        if (s.contains(":")) {
+            String[] parts = s.split(":", 2);
+            domain = parts[0];
+            path = parts[1];
+        } else {
+            domain = GroovyScript.getRunConfig().getPackId();
+            path = s;
+        }
+        return materialBuilder(event, id, new ResourceLocation(domain, path));
+    }
+}
diff --git a/src/main/java/gregtech/integration/groovy/GroovyMaterialBuilderExpansion.java b/src/main/java/gregtech/integration/groovy/GroovyMaterialBuilderExpansion.java
index 3f5ea4873f4..f4e30273435 100644
--- a/src/main/java/gregtech/integration/groovy/GroovyMaterialBuilderExpansion.java
+++ b/src/main/java/gregtech/integration/groovy/GroovyMaterialBuilderExpansion.java
@@ -8,9 +8,13 @@
 import gregtech.api.unification.material.info.MaterialFlag;
 import gregtech.api.unification.material.info.MaterialIconSet;
 import gregtech.api.unification.material.properties.BlastProperty;
+import gregtech.api.unification.stack.MaterialStack;
 
 import net.minecraft.util.ResourceLocation;
 
+import com.cleanroommc.groovyscript.api.GroovyLog;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -85,22 +89,30 @@ public static Material.Builder blastTemp(Material.Builder builder, int temp, Str
 
     public static Material.Builder blastTemp(Material.Builder builder, int temp, String raw, int eutOverride,
                                              int durationOverride, int vacuumEUtOverride, int vacuumDurationOverride) {
-        BlastProperty.GasTier gasTier = null;
-        String name = raw.toUpperCase();
-        for (BlastProperty.GasTier gasTier1 : BlastProperty.GasTier.VALUES) {
-            if (gasTier1.name().equals(name)) {
-                gasTier = gasTier1;
-                break;
-            }
-        }
-        final BlastProperty.GasTier finalGasTier = gasTier;
-        if (GroovyScriptModule.validateNonNull(gasTier, () -> "Can't find gas tier for " + name +
-                " in material builder. Valid values are 'low', 'mid', 'high', 'higher', 'highest'!")) {
+        BlastProperty.GasTier gasTier = GroovyScriptModule.parseAndValidateEnumValue(BlastProperty.GasTier.class, raw,
+                "gas tier");
+        if (gasTier != null) {
             return builder.blast(b -> b
-                    .temp(temp, finalGasTier)
+                    .temp(temp, gasTier)
                     .blastStats(eutOverride, durationOverride)
                     .vacuumStats(vacuumEUtOverride, vacuumDurationOverride));
         }
         return builder;
     }
+
+    public static Material.Builder components(Material.Builder builder, Object... objects) {
+        ObjectArrayList materialStacks = new ObjectArrayList<>();
+        for (Object o : objects) {
+            if (o instanceof MaterialStack materialStack) {
+                materialStacks.add(materialStack);
+            } else if (o instanceof Material material) {
+                materialStacks.add(new MaterialStack(material, 1));
+            } else {
+                GroovyLog.get()
+                        .error("Material components must be of type Material or MaterialStack, but was of type {}",
+                                o == null ? null : o.getClass());
+            }
+        }
+        return builder.components(materialStacks.toArray(new MaterialStack[0]));
+    }
 }
diff --git a/src/main/java/gregtech/integration/groovy/GroovyRecipeBuilderExpansion.java b/src/main/java/gregtech/integration/groovy/GroovyRecipeBuilderExpansion.java
deleted file mode 100644
index 90a0dc3753b..00000000000
--- a/src/main/java/gregtech/integration/groovy/GroovyRecipeBuilderExpansion.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package gregtech.integration.groovy;
-
-import gregtech.api.recipes.RecipeBuilder;
-
-import com.cleanroommc.groovyscript.api.GroovyLog;
-
-public class GroovyRecipeBuilderExpansion {
-
-    public static > RecipeBuilder property(RecipeBuilder builder, String key,
-                                                                         Object value) {
-        if (!builder.applyProperty(key, value)) {
-            GroovyLog.get().error("Failed to add property '{}' with '{}' to recipe", key, value);
-        }
-        return builder;
-    }
-}
diff --git a/src/main/java/gregtech/integration/groovy/GroovyScriptModule.java b/src/main/java/gregtech/integration/groovy/GroovyScriptModule.java
index adb308fe4e6..571261241b2 100644
--- a/src/main/java/gregtech/integration/groovy/GroovyScriptModule.java
+++ b/src/main/java/gregtech/integration/groovy/GroovyScriptModule.java
@@ -8,8 +8,11 @@
 import gregtech.api.recipes.RecipeBuilder;
 import gregtech.api.recipes.RecipeMap;
 import gregtech.api.unification.material.Material;
+import gregtech.api.unification.material.event.MaterialEvent;
+import gregtech.api.unification.material.event.PostMaterialEvent;
 import gregtech.api.unification.material.registry.MaterialRegistry;
 import gregtech.api.unification.ore.OrePrefix;
+import gregtech.api.util.Mods;
 import gregtech.common.blocks.BlockCompressed;
 import gregtech.common.blocks.BlockFrame;
 import gregtech.common.blocks.MetaBlocks;
@@ -34,25 +37,32 @@
 import com.cleanroommc.groovyscript.api.GroovyPlugin;
 import com.cleanroommc.groovyscript.api.IGameObjectHandler;
 import com.cleanroommc.groovyscript.compat.mods.GroovyContainer;
+import com.cleanroommc.groovyscript.compat.mods.ModPropertyContainer;
+import com.cleanroommc.groovyscript.event.EventBusType;
+import com.cleanroommc.groovyscript.event.GroovyEventManager;
 import com.cleanroommc.groovyscript.gameobjects.GameObjectHandlerManager;
+import com.cleanroommc.groovyscript.helper.EnumHelper;
+import com.cleanroommc.groovyscript.sandbox.LoadStage;
 import com.cleanroommc.groovyscript.sandbox.expand.ExpansionHelper;
 import com.google.common.collect.ImmutableList;
+import groovy.lang.Closure;
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.function.Supplier;
 
-@Optional.Interface(modid = GTValues.MODID_GROOVYSCRIPT,
+@Optional.Interface(modid = Mods.Names.GROOVY_SCRIPT,
                     iface = "com.cleanroommc.groovyscript.api.GroovyPlugin",
                     striprefs = true)
 @GregTechModule(
                 moduleID = GregTechModules.MODULE_GRS,
                 containerID = GTValues.MODID,
-                modDependencies = GTValues.MODID_GROOVYSCRIPT,
+                modDependencies = Mods.Names.GROOVY_SCRIPT,
                 name = "GregTech GroovyScript Integration",
                 description = "GroovyScript Integration Module")
 public class GroovyScriptModule extends IntegrationSubmodule implements GroovyPlugin {
@@ -76,6 +86,18 @@ public static boolean isCurrentlyRunning() {
                 GroovyScript.getSandbox().isRunning();
     }
 
+    public static > T parseAndValidateEnumValue(Class clazz, String raw, String type) {
+        T t = EnumHelper.valueOfNullable(clazz, raw, false);
+        if (t == null) {
+            GroovyLog.get().error("Can't find {} for {} in material builder. Valid values are {};",
+                    type,
+                    raw,
+                    Arrays.toString(clazz.getEnumConstants()));
+            return null;
+        }
+        return t;
+    }
+
     public static GroovyContainer getInstance() {
         return modSupportContainer;
     }
@@ -190,10 +212,42 @@ public static void loadMetaItemBracketHandler() {
     }
 
     @Override
-    public @NotNull String getModName() {
+    public @NotNull String getContainerName() {
         return "GregTech";
     }
 
+    @Override
+    public @Nullable ModPropertyContainer createModPropertyContainer() {
+        return new ModPropertyContainer() {
+
+            public void materialEvent(EventPriority priority, Closure eventListener) {
+                if (isCurrentlyRunning() && GroovyScript.getSandbox().getCurrentLoader() != LoadStage.PRE_INIT) {
+                    GroovyLog.get().error("GregTech's material event can only be used in pre init!");
+                    return;
+                }
+                GroovyEventManager.INSTANCE.listen(priority, EventBusType.FORGE, MaterialEvent.class,
+                        eventListener);
+            }
+
+            public void materialEvent(Closure eventListener) {
+                materialEvent(EventPriority.NORMAL, eventListener);
+            }
+
+            public void lateMaterialEvent(EventPriority priority, Closure eventListener) {
+                if (isCurrentlyRunning() && GroovyScript.getSandbox().getCurrentLoader() != LoadStage.PRE_INIT) {
+                    GroovyLog.get().error("GregTech's material event can only be used in pre init!");
+                    return;
+                }
+                GroovyEventManager.INSTANCE.listen(priority, EventBusType.FORGE, PostMaterialEvent.class,
+                        eventListener);
+            }
+
+            public void lateMaterialEvent(Closure eventListener) {
+                materialEvent(EventPriority.NORMAL, eventListener);
+            }
+        };
+    }
+
     @Override
     public void onCompatLoaded(GroovyContainer groovyContainer) {
         modSupportContainer = groovyContainer;
@@ -212,6 +266,7 @@ public void onCompatLoaded(GroovyContainer groovyContainer) {
         ExpansionHelper.mixinClass(Material.class, MaterialExpansion.class);
         ExpansionHelper.mixinClass(Material.class, MaterialPropertyExpansion.class);
         ExpansionHelper.mixinClass(Material.Builder.class, GroovyMaterialBuilderExpansion.class);
-        ExpansionHelper.mixinClass(RecipeBuilder.class, GroovyRecipeBuilderExpansion.class);
+        ExpansionHelper.mixinMethod(RecipeBuilder.class, GroovyExpansions.class, "property");
+        ExpansionHelper.mixinMethod(MaterialEvent.class, GroovyExpansions.class, "materialBuilder");
     }
 }
diff --git a/src/main/java/gregtech/integration/hwyla/HWYLAModule.java b/src/main/java/gregtech/integration/hwyla/HWYLAModule.java
index bce36365fc0..d26defcbde3 100644
--- a/src/main/java/gregtech/integration/hwyla/HWYLAModule.java
+++ b/src/main/java/gregtech/integration/hwyla/HWYLAModule.java
@@ -2,6 +2,7 @@
 
 import gregtech.api.GTValues;
 import gregtech.api.modules.GregTechModule;
+import gregtech.api.util.Mods;
 import gregtech.integration.IntegrationSubmodule;
 import gregtech.integration.hwyla.provider.*;
 import gregtech.modules.GregTechModules;
@@ -17,7 +18,7 @@
 @GregTechModule(
                 moduleID = GregTechModules.MODULE_HWYLA,
                 containerID = GTValues.MODID,
-                modDependencies = GTValues.MODID_HWYLA,
+                modDependencies = Mods.Names.HWYLA,
                 name = "GregTech HWYLA Integration",
                 description = "HWYLA (WAILA) Integration Module")
 public class HWYLAModule extends IntegrationSubmodule implements IWailaPlugin {
diff --git a/src/main/java/gregtech/integration/hwyla/provider/ConverterDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/ConverterDataProvider.java
index 341334681c7..593832936cc 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/ConverterDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/ConverterDataProvider.java
@@ -4,6 +4,7 @@
 import gregtech.api.capability.FeCompat;
 import gregtech.api.capability.GregtechCapabilities;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.TextFormattingUtil;
 import gregtech.common.metatileentities.converter.ConverterTrait;
 
 import net.minecraft.client.resources.I18n;
@@ -67,19 +68,21 @@ protected NBTTagCompound getNBTData(ConverterTrait capability, NBTTagCompound ta
                 tooltip.add(I18n.format("gregtech.top.convert_fe"));
                 if (accessor.getSide() == frontFacing) {
                     tooltip.add(I18n.format("gregtech.top.transform_output") + " " + voltageName +
-                            TextFormatting.RESET + " (" + amperage + "A)");
+                            TextFormatting.RESET + " (" + TextFormattingUtil.formatNumbers(amperage) + "A)");
                 } else {
                     tooltip.add(I18n.format("gregtech.top.transform_input") + " " +
-                            FeCompat.toFe(voltage * amperage, FeCompat.ratio(true)) + " FE");
+                            TextFormattingUtil.formatNumbers(FeCompat.toFe(voltage * amperage, FeCompat.ratio(true))) +
+                            " FE");
                 }
             } else {
                 tooltip.add(I18n.format("gregtech.top.convert_eu"));
                 if (accessor.getSide() == frontFacing) {
                     tooltip.add(I18n.format("gregtech.top.transform_output") + " " +
-                            FeCompat.toFe(voltage * amperage, FeCompat.ratio(false)) + " FE");
+                            TextFormattingUtil.formatNumbers(FeCompat.toFe(voltage * amperage, FeCompat.ratio(false))) +
+                            " FE");
                 } else {
                     tooltip.add(I18n.format("gregtech.top.transform_input") + " " + voltageName + TextFormatting.RESET +
-                            " (" + amperage + "A)");
+                            " (" + TextFormattingUtil.formatNumbers(amperage) + "A)");
                 }
             }
         }
diff --git a/src/main/java/gregtech/integration/hwyla/provider/DiodeDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/DiodeDataProvider.java
index 1000a11ff55..69e38bfaad7 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/DiodeDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/DiodeDataProvider.java
@@ -3,6 +3,7 @@
 import gregtech.api.GTValues;
 import gregtech.api.capability.IEnergyContainer;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+import gregtech.api.util.TextFormattingUtil;
 import gregtech.common.metatileentities.electric.MetaTileEntityDiode;
 
 import net.minecraft.client.resources.I18n;
@@ -59,9 +60,11 @@ protected NBTTagCompound getNBTData(IEnergyContainer capability, NBTTagCompound
             final EnumFacing frontFacing = EnumFacing.byIndex(tag.getInteger("FrontFacing"));
 
             if (accessor.getSide() == frontFacing) { // output side
-                tooltip.add(I18n.format("gregtech.top.transform_output") + " " + outputAmperage + " A");
+                tooltip.add(I18n.format("gregtech.top.transform_output") + " " +
+                        TextFormattingUtil.formatNumbers(outputAmperage) + " A");
             } else {
-                tooltip.add(I18n.format("gregtech.top.transform_input") + " " + inputAmperage + " A");
+                tooltip.add(I18n.format("gregtech.top.transform_input") + " " +
+                        TextFormattingUtil.formatNumbers(inputAmperage) + " A");
             }
         }
         return tooltip;
diff --git a/src/main/java/gregtech/integration/hwyla/provider/PrimitivePumpDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/PrimitivePumpDataProvider.java
index 4af2e2f8915..3598647818f 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/PrimitivePumpDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/PrimitivePumpDataProvider.java
@@ -3,6 +3,7 @@
 import gregtech.api.GTValues;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IPrimitivePump;
+import gregtech.api.util.TextFormattingUtil;
 
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayerMP;
@@ -58,7 +59,8 @@ public List getWailaBody(ItemStack itemStack, List tooltip, IWai
         if (accessor.getNBTData().hasKey("gregtech.IPrimitivePump")) {
             NBTTagCompound tag = accessor.getNBTData().getCompoundTag("gregtech.IPrimitivePump");
             int production = tag.getInteger("Production");
-            tooltip.add(I18n.format("gregtech.top.primitive_pump_production") + " " + TextFormatting.AQUA + production +
+            tooltip.add(I18n.format("gregtech.top.primitive_pump_production") + " " + TextFormatting.AQUA +
+                    TextFormattingUtil.formatNumbers(production) +
                     TextFormatting.RESET + " L/s");
         }
         return tooltip;
diff --git a/src/main/java/gregtech/integration/hwyla/provider/RecipeLogicDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/RecipeLogicDataProvider.java
index f1a2a6b5a4c..655dd57aa20 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/RecipeLogicDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/RecipeLogicDataProvider.java
@@ -9,6 +9,7 @@
 import gregtech.api.metatileentity.multiblock.RecipeMapSteamMultiblockController;
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.TextFormattingUtil;
 import gregtech.common.metatileentities.multi.MetaTileEntityLargeBoiler;
 
 import net.minecraft.client.resources.I18n;
@@ -72,7 +73,7 @@ public List getWailaBody(ItemStack itemStack, List tooltip, IWai
                     MetaTileEntity mte = gtte.getMetaTileEntity();
                     if (mte instanceof SteamMetaTileEntity || mte instanceof MetaTileEntityLargeBoiler ||
                             mte instanceof RecipeMapSteamMultiblockController) {
-                        endText = ": " + absEUt + TextFormatting.RESET + " L/t " +
+                        endText = ": " + TextFormattingUtil.formatNumbers(absEUt) + TextFormatting.RESET + " L/t " +
                                 I18n.format(Materials.Steam.getUnlocalizedName());
                     }
                     AbstractRecipeLogic arl = mte.getRecipeLogic();
@@ -81,7 +82,7 @@ public List getWailaBody(ItemStack itemStack, List tooltip, IWai
                     }
                 }
                 if (endText == null) {
-                    endText = ": " + absEUt + TextFormatting.RESET + " EU/t (" +
+                    endText = ": " + TextFormattingUtil.formatNumbers(absEUt) + TextFormatting.RESET + " EU/t (" +
                             GTValues.VNF[GTUtility.getTierByVoltage(absEUt)] + TextFormatting.RESET + ")";
                 }
 
diff --git a/src/main/java/gregtech/integration/hwyla/provider/SteamBoilerDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/SteamBoilerDataProvider.java
index 85a3f738ca0..0baaab2927f 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/SteamBoilerDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/SteamBoilerDataProvider.java
@@ -3,6 +3,7 @@
 import gregtech.api.GTValues;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.unification.material.Materials;
+import gregtech.api.util.TextFormattingUtil;
 import gregtech.common.metatileentities.steam.boiler.SteamBoiler;
 
 import net.minecraft.client.resources.I18n;
@@ -65,7 +66,8 @@ public List getWailaBody(ItemStack itemStack, List tooltip, IWai
 
                 // Creating steam
                 if (steamRate > 0 && hasWater) {
-                    tooltip.add(I18n.format("gregtech.top.energy_production") + ": " + (steamRate / 10) + " L/t " +
+                    tooltip.add(I18n.format("gregtech.top.energy_production") + ": " +
+                            TextFormattingUtil.formatNumbers(steamRate / 10) + " L/t " +
                             I18n.format(Materials.Steam.getUnlocalizedName()));
                 }
 
diff --git a/src/main/java/gregtech/integration/hwyla/provider/TransformerDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/TransformerDataProvider.java
index 49da30bf2da..e54792f8139 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/TransformerDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/TransformerDataProvider.java
@@ -4,6 +4,7 @@
 import gregtech.api.capability.IEnergyContainer;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.TextFormattingUtil;
 import gregtech.common.metatileentities.electric.MetaTileEntityTransformer;
 
 import net.minecraft.client.resources.I18n;
@@ -82,7 +83,7 @@ public List getWailaBody(ItemStack itemStack, List tooltip, IWai
                     .append(GTValues.VNF[GTUtility.getTierByVoltage(inputVoltage)])
                     .append(TextFormatting.RESET)
                     .append(" (")
-                    .append(inputAmperage)
+                    .append(TextFormattingUtil.formatNumbers(inputAmperage))
                     .append("A)");
 
             StringBuilder output = new StringBuilder()
@@ -90,7 +91,7 @@ public List getWailaBody(ItemStack itemStack, List tooltip, IWai
                     .append(GTValues.VNF[GTUtility.getTierByVoltage(outputVoltage)])
                     .append(TextFormatting.RESET)
                     .append(" (")
-                    .append(outputAmperage)
+                    .append(TextFormattingUtil.formatNumbers(outputAmperage))
                     .append("A)");
 
             // Step Up/Step Down line
diff --git a/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java b/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java
index be7c1bd50ca..491db7fb1f1 100644
--- a/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java
+++ b/src/main/java/gregtech/integration/jei/JustEnoughItemsModule.java
@@ -20,6 +20,7 @@
 import gregtech.api.recipes.machines.RecipeMapFurnace;
 import gregtech.api.unification.material.Material;
 import gregtech.api.unification.material.properties.PropertyKey;
+import gregtech.api.util.Mods;
 import gregtech.api.worldgen.config.BedrockFluidDepositDefinition;
 import gregtech.api.worldgen.config.OreDepositDefinition;
 import gregtech.api.worldgen.config.WorldGenRegistry;
@@ -74,10 +75,10 @@
 import org.jetbrains.annotations.NotNull;
 
 import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -85,7 +86,7 @@
 @GregTechModule(
                 moduleID = GregTechModules.MODULE_JEI,
                 containerID = GTValues.MODID,
-                modDependencies = GTValues.MODID_JEI,
+                modDependencies = Mods.Names.JUST_ENOUGH_ITEMS,
                 name = "GregTech JEI Integration",
                 description = "JustEnoughItems Integration Module")
 public class JustEnoughItemsModule extends IntegrationSubmodule implements IModPlugin {
@@ -214,7 +215,7 @@ public void register(IModRegistry registry) {
         registry.addRecipeCatalyst(MetaTileEntities.LARGE_TITANIUM_BOILER.getStackForm(), semiFluidMapId);
         registry.addRecipeCatalyst(MetaTileEntities.LARGE_TUNGSTENSTEEL_BOILER.getStackForm(), semiFluidMapId);
 
-        List oreByproductList = new CopyOnWriteArrayList<>();
+        List oreByproductList = new ArrayList<>();
         for (Material material : GregTechAPI.materialManager.getRegisteredMaterials()) {
             if (material.hasProperty(PropertyKey.ORE)) {
                 oreByproductList.add(new OreByProduct(material));
@@ -222,7 +223,7 @@ public void register(IModRegistry registry) {
         }
         String oreByProductId = GTValues.MODID + ":" + "ore_by_product";
         registry.addRecipes(oreByproductList, oreByProductId);
-        MetaTileEntity[][] machineLists = new MetaTileEntity[][] {
+        MetaTileEntity[][] machineLists = {
                 MetaTileEntities.MACERATOR,
                 MetaTileEntities.ORE_WASHER,
                 MetaTileEntities.CENTRIFUGE,
@@ -237,7 +238,7 @@ public void register(IModRegistry registry) {
         }
 
         // Material Tree
-        List materialTreeList = new CopyOnWriteArrayList<>();
+        List materialTreeList = new ArrayList<>();
         for (Material material : GregTechAPI.materialManager.getRegisteredMaterials()) {
             if (material.hasProperty(PropertyKey.DUST)) {
                 materialTreeList.add(new MaterialTree(material));
@@ -247,7 +248,7 @@ public void register(IModRegistry registry) {
 
         // Ore Veins
         List oreVeins = WorldGenRegistry.getOreDeposits();
-        List oreInfoList = new CopyOnWriteArrayList<>();
+        List oreInfoList = new ArrayList<>();
         for (OreDepositDefinition vein : oreVeins) {
             oreInfoList.add(new GTOreInfo(vein));
         }
@@ -261,7 +262,7 @@ public void register(IModRegistry registry) {
 
         // Fluid Veins
         List fluidVeins = WorldGenRegistry.getBedrockVeinDeposits();
-        List fluidVeinInfos = new CopyOnWriteArrayList<>();
+        List fluidVeinInfos = new ArrayList<>();
         for (BedrockFluidDepositDefinition fluidVein : fluidVeins) {
             fluidVeinInfos.add(new GTFluidVeinInfo(fluidVein));
         }
@@ -307,7 +308,7 @@ private void setupInputHandler() {
 
             showsRecipeFocuses.add(new MultiblockInfoRecipeFocusShower());
 
-        } catch (Exception e) {
+        } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | SecurityException e) {
             getLogger().error("Could not reflect JEI Internal inputHandler", e);
         }
     }
diff --git a/src/main/java/gregtech/integration/jei/basic/GTOreInfo.java b/src/main/java/gregtech/integration/jei/basic/GTOreInfo.java
index 0f5bd90bf9f..75d8c11f81a 100644
--- a/src/main/java/gregtech/integration/jei/basic/GTOreInfo.java
+++ b/src/main/java/gregtech/integration/jei/basic/GTOreInfo.java
@@ -4,6 +4,7 @@
 import gregtech.api.unification.material.Material;
 import gregtech.api.util.FileUtility;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.Mods;
 import gregtech.api.worldgen.config.FillerConfigUtils;
 import gregtech.api.worldgen.config.OreDepositDefinition;
 import gregtech.api.worldgen.filler.BlockFiller;
@@ -26,7 +27,6 @@
 import net.minecraftforge.fluids.FluidStack;
 import net.minecraftforge.fluids.FluidUtil;
 import net.minecraftforge.fluids.IFluidBlock;
-import net.minecraftforge.fml.common.Loader;
 
 import com.google.common.collect.ImmutableList;
 import mezz.jei.api.ingredients.IIngredients;
@@ -41,7 +41,6 @@
 import java.util.function.Function;
 
 import static gregtech.api.GTValues.M;
-import static gregtech.api.GTValues.MODID_CC;
 
 public class GTOreInfo implements IRecipeWrapper {
 
@@ -64,7 +63,7 @@ public GTOreInfo(OreDepositDefinition definition) {
 
         // Don't default to vanilla Maximums and minimums if the values are not defined and Cubic Chunks is loaded
         // This could be improved to use the actual minimum and maximum heights, at the cost of including the CC Api
-        if (Loader.isModLoaded(MODID_CC)) {
+        if (Mods.CubicChunks.isModLoaded()) {
             this.maxHeight = definition.getMaximumHeight() == Integer.MAX_VALUE ? Integer.MAX_VALUE :
                     definition.getMaximumHeight();
             this.minHeight = definition.getMinimumHeight() == Integer.MIN_VALUE ? Integer.MIN_VALUE :
diff --git a/src/main/java/gregtech/integration/jei/basic/MaterialTree.java b/src/main/java/gregtech/integration/jei/basic/MaterialTree.java
index 3babd634779..82cc58d5dab 100644
--- a/src/main/java/gregtech/integration/jei/basic/MaterialTree.java
+++ b/src/main/java/gregtech/integration/jei/basic/MaterialTree.java
@@ -6,6 +6,7 @@
 import gregtech.api.unification.ore.OrePrefix;
 
 import net.minecraft.item.ItemStack;
+import net.minecraftforge.fluids.Fluid;
 import net.minecraftforge.fluids.FluidStack;
 
 import com.google.common.collect.ImmutableList;
@@ -74,7 +75,10 @@ public MaterialTree(Material material) {
 
         List matFluidsStack = new ArrayList<>();
         if (material.hasProperty(PropertyKey.FLUID)) {
-            matFluidsStack.add(material.getFluid(1000));
+            Fluid fluid = material.getFluid();
+            if (fluid != null) {
+                matFluidsStack.add(new FluidStack(fluid, 1000));
+            }
         }
         this.fluidInputs.add(matFluidsStack);
 
diff --git a/src/main/java/gregtech/integration/jei/utils/JEIResourceDepositCategoryUtils.java b/src/main/java/gregtech/integration/jei/utils/JEIResourceDepositCategoryUtils.java
index 77ee3c8bcb7..bc5042b0d64 100644
--- a/src/main/java/gregtech/integration/jei/utils/JEIResourceDepositCategoryUtils.java
+++ b/src/main/java/gregtech/integration/jei/utils/JEIResourceDepositCategoryUtils.java
@@ -1,6 +1,7 @@
 package gregtech.integration.jei.utils;
 
 import gregtech.api.util.GTLog;
+import gregtech.api.util.Mods;
 import gregtech.api.worldgen.config.OreDepositDefinition;
 
 import net.minecraft.client.Minecraft;
@@ -10,7 +11,6 @@
 import net.minecraft.world.WorldProvider;
 import net.minecraft.world.biome.Biome;
 import net.minecraftforge.common.DimensionManager;
-import net.minecraftforge.fml.common.Loader;
 
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -27,8 +27,6 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-import static gregtech.api.GTValues.MODID_AR;
-
 /**
  * Common util methods shared between {@link gregtech.integration.jei.basic.GTOreCategory}
  * and {@link gregtech.integration.jei.basic.GTFluidVeinCategory}
@@ -139,7 +137,7 @@ public static int[] getAllRegisteredDimensions(@Nullable Predicate {
@@ -31,10 +33,10 @@ protected void addProbeInfo(ILaserContainer capability, IProbeInfo probeInfo, En
         long maxStorage = capability.getEnergyCapacity();
         if (maxStorage == 0) return; // do not add empty max storage progress bar
         probeInfo.progress(capability.getEnergyStored(), maxStorage, probeInfo.defaultProgressStyle()
-                .suffix(" / " + maxStorage + " EU")
+                .suffix(" / " + TextFormattingUtil.formatNumbers(maxStorage) + " EU")
                 .filledColor(0xFFEEE600)
                 .alternateFilledColor(0xFFEEE600)
-                .borderColor(0xFF555555));
+                .borderColor(0xFF555555).numberFormat(NumberFormat.COMMAS));
     }
 
     @Override
diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/PrimitivePumpInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/PrimitivePumpInfoProvider.java
index ea20f8be8a8..3c7d3d79004 100644
--- a/src/main/java/gregtech/integration/theoneprobe/provider/PrimitivePumpInfoProvider.java
+++ b/src/main/java/gregtech/integration/theoneprobe/provider/PrimitivePumpInfoProvider.java
@@ -4,6 +4,7 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IPrimitivePump;
+import gregtech.api.util.TextFormattingUtil;
 
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.entity.player.EntityPlayer;
@@ -32,7 +33,9 @@ public void addProbeInfo(@NotNull ProbeMode mode, @NotNull IProbeInfo probeInfo,
             if (metaTileEntity instanceof IPrimitivePump) {
                 probeInfo.text(
                         TextStyleClass.INFO + "{*gregtech.top.primitive_pump_production*} " + TextFormatting.AQUA +
-                                ((IPrimitivePump) metaTileEntity).getFluidProduction() + TextFormatting.RESET + " L/s");
+                                TextFormattingUtil
+                                        .formatNumbers(((IPrimitivePump) metaTileEntity).getFluidProduction()) +
+                                TextFormatting.RESET + " L/s");
             }
         }
     }
diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/RecipeLogicInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/RecipeLogicInfoProvider.java
index 8ea1a0ac3c7..3a60fab7db6 100644
--- a/src/main/java/gregtech/integration/theoneprobe/provider/RecipeLogicInfoProvider.java
+++ b/src/main/java/gregtech/integration/theoneprobe/provider/RecipeLogicInfoProvider.java
@@ -10,6 +10,7 @@
 import gregtech.api.metatileentity.multiblock.RecipeMapSteamMultiblockController;
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.TextFormattingUtil;
 import gregtech.common.metatileentities.multi.MetaTileEntityLargeBoiler;
 
 import net.minecraft.entity.player.EntityPlayer;
@@ -53,14 +54,16 @@ protected void addProbeInfo(@NotNull AbstractRecipeLogic capability, @NotNull IP
                 MetaTileEntity mte = gtTileEntity.getMetaTileEntity();
                 if (mte instanceof SteamMetaTileEntity || mte instanceof MetaTileEntityLargeBoiler ||
                         mte instanceof RecipeMapSteamMultiblockController) {
-                    text = TextFormatting.AQUA.toString() + absEUt + TextStyleClass.INFO + " L/t {*" +
+                    text = TextFormatting.AQUA.toString() + TextFormattingUtil.formatNumbers(absEUt) +
+                            TextStyleClass.INFO + " L/t {*" +
                             Materials.Steam.getUnlocalizedName() + "*}";
                 }
             }
             if (text == null) {
                 // Default behavior, if this TE is not a steam machine (or somehow not instanceof
                 // IGregTechTileEntity...)
-                text = TextFormatting.RED.toString() + absEUt + TextStyleClass.INFO + " EU/t" + TextFormatting.GREEN +
+                text = TextFormatting.RED.toString() + TextFormattingUtil.formatNumbers(absEUt) + TextStyleClass.INFO +
+                        " EU/t" + TextFormatting.GREEN +
                         " (" + GTValues.VNF[GTUtility.getTierByVoltage(absEUt)] + TextFormatting.GREEN + ")";
             }
 
diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/SteamBoilerInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/SteamBoilerInfoProvider.java
index 4ccdc7125d0..248d0fc1d02 100644
--- a/src/main/java/gregtech/integration/theoneprobe/provider/SteamBoilerInfoProvider.java
+++ b/src/main/java/gregtech/integration/theoneprobe/provider/SteamBoilerInfoProvider.java
@@ -4,6 +4,7 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.unification.material.Materials;
+import gregtech.api.util.TextFormattingUtil;
 import gregtech.common.metatileentities.steam.boiler.SteamBoiler;
 
 import net.minecraft.block.state.IBlockState;
@@ -36,7 +37,8 @@ public void addProbeInfo(ProbeMode mode, IProbeInfo probeInfo, EntityPlayer play
                         // Creating steam
                         if (steamOutput > 0 && boiler.hasWater()) {
                             probeInfo.text(TextStyleClass.INFO + "{*gregtech.top.energy_production*} " +
-                                    TextFormatting.AQUA + (steamOutput / 10) + TextStyleClass.INFO + " L/t" + " {*" +
+                                    TextFormatting.AQUA + TextFormattingUtil.formatNumbers(steamOutput / 10) +
+                                    TextStyleClass.INFO + " L/t" + " {*" +
                                     Materials.Steam.getUnlocalizedName() + "*}");
                         }
 
diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/TransformerInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/TransformerInfoProvider.java
index cf77786cc15..0cf94adc4bd 100644
--- a/src/main/java/gregtech/integration/theoneprobe/provider/TransformerInfoProvider.java
+++ b/src/main/java/gregtech/integration/theoneprobe/provider/TransformerInfoProvider.java
@@ -5,6 +5,7 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.TextFormattingUtil;
 import gregtech.common.metatileentities.electric.MetaTileEntityTransformer;
 
 import net.minecraft.entity.player.EntityPlayer;
@@ -33,14 +34,14 @@ protected void addProbeInfo(@NotNull IEnergyContainer capability, @NotNull IProb
                         .append(GTValues.VNF[GTUtility.getTierByVoltage(capability.getInputVoltage())])
                         .append(TextFormatting.GREEN)
                         .append(" (")
-                        .append(capability.getInputAmperage())
+                        .append(TextFormattingUtil.formatNumbers(capability.getInputAmperage()))
                         .append("A)");
 
                 StringBuilder output = new StringBuilder()
                         .append(GTValues.VNF[GTUtility.getTierByVoltage(capability.getOutputVoltage())])
                         .append(TextFormatting.GREEN)
                         .append(" (")
-                        .append(capability.getOutputAmperage())
+                        .append(TextFormattingUtil.formatNumbers(capability.getOutputAmperage()))
                         .append("A)");
 
                 // Step Up/Step Down line
diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/WorkableInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/WorkableInfoProvider.java
index 5d3c500e81e..ac6254aa8b5 100644
--- a/src/main/java/gregtech/integration/theoneprobe/provider/WorkableInfoProvider.java
+++ b/src/main/java/gregtech/integration/theoneprobe/provider/WorkableInfoProvider.java
@@ -4,6 +4,7 @@
 import gregtech.api.capability.GregtechTileCapabilities;
 import gregtech.api.capability.IWorkable;
 import gregtech.api.capability.impl.ComputationRecipeLogic;
+import gregtech.api.util.TextFormattingUtil;
 
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.tileentity.TileEntity;
@@ -11,6 +12,7 @@
 
 import mcjty.theoneprobe.api.IProbeHitData;
 import mcjty.theoneprobe.api.IProbeInfo;
+import mcjty.theoneprobe.api.NumberFormat;
 import org.jetbrains.annotations.NotNull;
 
 public class WorkableInfoProvider extends CapabilityInfoProvider {
@@ -52,7 +54,7 @@ protected void addProbeInfo(@NotNull IWorkable capability, @NotNull IProbeInfo p
         } else {
             currentProgress = Math.round(currentProgress / 20.0F);
             maxProgress = Math.round(maxProgress / 20.0F);
-            text = " / " + maxProgress + " s";
+            text = " / " + TextFormattingUtil.formatNumbers(maxProgress) + " s";
         }
 
         if (maxProgress > 0) {
@@ -61,7 +63,7 @@ protected void addProbeInfo(@NotNull IWorkable capability, @NotNull IProbeInfo p
                     .suffix(text)
                     .filledColor(color)
                     .alternateFilledColor(color)
-                    .borderColor(0xFF555555));
+                    .borderColor(0xFF555555).numberFormat(NumberFormat.COMMAS));
         }
     }
 }
diff --git a/src/main/java/gregtech/loaders/OreDictionaryLoader.java b/src/main/java/gregtech/loaders/OreDictionaryLoader.java
index b05fba4609b..a902983af39 100644
--- a/src/main/java/gregtech/loaders/OreDictionaryLoader.java
+++ b/src/main/java/gregtech/loaders/OreDictionaryLoader.java
@@ -25,12 +25,14 @@ public class OreDictionaryLoader {
 
     public static final String OREDICT_FUEL_COKE = "fuelCoke";
     public static final String OREDICT_BLOCK_FUEL_COKE = "blockFuelCoke";
+    public static final String OREDICT_BLOCK_COAL_COKE = "blockCoalCoke";
 
     public static void init() {
         GTLog.logger.info("Registering OreDict entries.");
 
         OreDictionary.registerOre(OREDICT_FUEL_COKE, OreDictUnifier.get(OrePrefix.gem, Materials.Coke));
         OreDictionary.registerOre(OREDICT_BLOCK_FUEL_COKE, OreDictUnifier.get(OrePrefix.block, Materials.Coke));
+        OreDictionary.registerOre(OREDICT_BLOCK_COAL_COKE, OreDictUnifier.get(OrePrefix.block, Materials.Coke));
         OreDictionary.registerOre("crystalCertusQuartz", OreDictUnifier.get(OrePrefix.gem, Materials.CertusQuartz));
 
         OreDictUnifier.registerOre(new ItemStack(Blocks.CLAY), OrePrefix.block, Materials.Clay);
diff --git a/src/main/java/gregtech/loaders/recipe/CraftingRecipeLoader.java b/src/main/java/gregtech/loaders/recipe/CraftingRecipeLoader.java
index 0b0d513a7f7..8b18ba7a811 100644
--- a/src/main/java/gregtech/loaders/recipe/CraftingRecipeLoader.java
+++ b/src/main/java/gregtech/loaders/recipe/CraftingRecipeLoader.java
@@ -336,6 +336,11 @@ private static void loadCraftingRecipes() {
                 new UnificationEntry(OrePrefix.wireGtQuadruple, Osmium), 'P',
                 new UnificationEntry(OrePrefix.plateDouble, Iridium), 'O',
                 MetaItems.ENERGY_LAPOTRONIC_ORB.getStackForm());
+
+        ModHandler.addShapedRecipe("powderbarrel", new ItemStack(MetaBlocks.POWDERBARREL), "PSP", "GGG", "PGP",
+                'P', new UnificationEntry(OrePrefix.plate, Wood),
+                'S', new ItemStack(Items.STRING),
+                'G', new UnificationEntry(OrePrefix.dust, Gunpowder));
     }
 
     private static void registerFacadeRecipe(Material material, int facadeAmount) {
diff --git a/src/main/java/gregtech/loaders/recipe/FuelRecipes.java b/src/main/java/gregtech/loaders/recipe/FuelRecipes.java
index 94da5ecb799..740521e8657 100644
--- a/src/main/java/gregtech/loaders/recipe/FuelRecipes.java
+++ b/src/main/java/gregtech/loaders/recipe/FuelRecipes.java
@@ -291,6 +291,13 @@ public static void registerFuels() {
         RecipeMaps.PLASMA_GENERATOR_FUELS.recipeBuilder()
                 .fluidInputs(Iron.getPlasma(1))
                 .fluidOutputs(Iron.getFluid(1))
+                .duration(112)
+                .EUt((int) V[EV])
+                .buildAndRegister();
+
+        RecipeMaps.PLASMA_GENERATOR_FUELS.recipeBuilder()
+                .fluidInputs(Tin.getPlasma(1))
+                .fluidOutputs(Tin.getFluid(1))
                 .duration(128)
                 .EUt((int) V[EV])
                 .buildAndRegister();
@@ -301,5 +308,12 @@ public static void registerFuels() {
                 .duration(192)
                 .EUt((int) V[EV])
                 .buildAndRegister();
+
+        RecipeMaps.PLASMA_GENERATOR_FUELS.recipeBuilder()
+                .fluidInputs(Americium.getPlasma(1))
+                .fluidOutputs(Americium.getFluid(1))
+                .duration(320)
+                .EUt((int) V[EV])
+                .buildAndRegister();
     }
 }
diff --git a/src/main/java/gregtech/loaders/recipe/FusionLoader.java b/src/main/java/gregtech/loaders/recipe/FusionLoader.java
index f5ec3a7b861..49515136876 100644
--- a/src/main/java/gregtech/loaders/recipe/FusionLoader.java
+++ b/src/main/java/gregtech/loaders/recipe/FusionLoader.java
@@ -38,16 +38,16 @@ public static void init() {
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
                 .fluidInputs(Materials.Silicon.getFluid(16))
                 .fluidInputs(Materials.Magnesium.getFluid(16))
-                .fluidOutputs(Materials.Iron.getPlasma(16))
+                .fluidOutputs(Materials.Iron.getPlasma(144))
                 .duration(32)
                 .EUt(VA[IV])
-                .EUToStart(360_000_000)
+                .EUToStart(300_000_000)
                 .buildAndRegister();
 
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
                 .fluidInputs(Materials.Potassium.getFluid(16))
                 .fluidInputs(Materials.Fluorine.getFluid(125))
-                .fluidOutputs(Materials.Nickel.getPlasma(16))
+                .fluidOutputs(Materials.Nickel.getPlasma(144))
                 .duration(16)
                 .EUt(VA[LuV])
                 .EUToStart(480_000_000)
@@ -62,6 +62,24 @@ public static void init() {
                 .EUToStart(180_000_000)
                 .buildAndRegister();
 
+        RecipeMaps.FUSION_RECIPES.recipeBuilder()
+                .fluidInputs(Materials.Plutonium241.getFluid(144))
+                .fluidInputs(Materials.Hydrogen.getFluid(2000))
+                .fluidOutputs(Materials.Americium.getPlasma(144))
+                .duration(64)
+                .EUt(98304)
+                .EUToStart(500_000_000)
+                .buildAndRegister();
+
+        RecipeMaps.FUSION_RECIPES.recipeBuilder()
+                .fluidInputs(Materials.Silver.getFluid(144))
+                .fluidInputs(Materials.Helium3.getFluid(375))
+                .fluidOutputs(Materials.Tin.getPlasma(144))
+                .duration(16)
+                .EUt(49152)
+                .EUToStart(280_000_000)
+                .buildAndRegister();
+
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
                 .fluidInputs(Materials.Neodymium.getFluid(16))
                 .fluidInputs(Materials.Hydrogen.getFluid(375))
@@ -72,9 +90,9 @@ public static void init() {
                 .buildAndRegister();
 
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
-                .fluidInputs(Materials.Lutetium.getFluid(32))
-                .fluidInputs(Materials.Chrome.getFluid(32))
-                .fluidOutputs(Materials.Americium.getFluid(32))
+                .fluidInputs(Materials.Lutetium.getFluid(16))
+                .fluidInputs(Materials.Chrome.getFluid(16))
+                .fluidOutputs(Materials.Americium.getFluid(16))
                 .duration(64)
                 .EUt(49152)
                 .EUToStart(200_000_000)
@@ -147,16 +165,16 @@ public static void init() {
                 .fluidInputs(Materials.Gallium.getFluid(16))
                 .fluidInputs(Materials.Radon.getFluid(125))
                 .fluidOutputs(Materials.Duranium.getFluid(16))
-                .duration(64)
+                .duration(32)
                 .EUt(16384)
                 .EUToStart(140_000_000)
                 .buildAndRegister();
 
         RecipeMaps.FUSION_RECIPES.recipeBuilder()
-                .fluidInputs(Materials.Titanium.getFluid(32))
+                .fluidInputs(Materials.Titanium.getFluid(48))
                 .fluidInputs(Materials.Duranium.getFluid(32))
                 .fluidOutputs(Materials.Tritanium.getFluid(16))
-                .duration(64)
+                .duration(16)
                 .EUt(VA[LuV])
                 .EUToStart(200_000_000)
                 .buildAndRegister();
diff --git a/src/main/java/gregtech/loaders/recipe/MachineRecipeLoader.java b/src/main/java/gregtech/loaders/recipe/MachineRecipeLoader.java
index eb820fbe624..cd7b866c272 100644
--- a/src/main/java/gregtech/loaders/recipe/MachineRecipeLoader.java
+++ b/src/main/java/gregtech/loaders/recipe/MachineRecipeLoader.java
@@ -15,6 +15,7 @@
 import gregtech.api.unification.ore.OrePrefix;
 import gregtech.api.unification.stack.MaterialStack;
 import gregtech.api.unification.stack.UnificationEntry;
+import gregtech.api.util.Mods;
 import gregtech.common.ConfigHolder;
 import gregtech.common.blocks.*;
 import gregtech.common.blocks.BlockMachineCasing.MachineCasingType;
@@ -34,7 +35,6 @@
 import net.minecraft.item.EnumDyeColor;
 import net.minecraft.item.ItemStack;
 import net.minecraftforge.fluids.FluidStack;
-import net.minecraftforge.fml.common.Loader;
 import net.minecraftforge.oredict.OreDictionary;
 
 import java.util.Arrays;
@@ -1461,7 +1461,7 @@ private static void ConvertHatchToHatch() {
             }
         }
 
-        if (Loader.isModLoaded(MODID_APPENG)) {
+        if (Mods.AppliedEnergistics2.isModLoaded()) {
             ModHandler.addShapedRecipe("me_fluid_hatch_output_to_input", FLUID_IMPORT_HATCH_ME.getStackForm(), "d", "B",
                     'B', FLUID_EXPORT_HATCH_ME.getStackForm());
             ModHandler.addShapedRecipe("me_fluid_hatch_input_to_output", FLUID_EXPORT_HATCH_ME.getStackForm(), "d", "B",
diff --git a/src/main/java/gregtech/loaders/recipe/MetaTileEntityLoader.java b/src/main/java/gregtech/loaders/recipe/MetaTileEntityLoader.java
index 9cb0e321a0e..192c35d1d16 100644
--- a/src/main/java/gregtech/loaders/recipe/MetaTileEntityLoader.java
+++ b/src/main/java/gregtech/loaders/recipe/MetaTileEntityLoader.java
@@ -898,7 +898,8 @@ public static void init() {
                 ROTOR);
 
         registerMachineRecipe(ArrayUtils.subarray(MetaTileEntities.DIODES, GTValues.ULV, GTValues.HV), "CDC", "DHD",
-                "PDP", 'H', HULL, 'D', MetaItems.DIODE, 'P', PLATE, 'C', CABLE_QUAD);
+                "PDP", 'H', HULL, 'D', new UnificationEntry(OrePrefix.component, MarkerMaterials.Component.Diode), 'P',
+                PLATE, 'C', CABLE_QUAD);
         registerMachineRecipe(ArrayUtils.subarray(MetaTileEntities.DIODES, GTValues.HV, GTValues.LuV), "CDC", "DHD",
                 "PDP", 'H', HULL, 'D', MetaItems.SMD_DIODE, 'P', PLATE, 'C', CABLE_QUAD);
         registerMachineRecipe(
diff --git a/src/main/java/gregtech/loaders/recipe/MetaTileEntityMachineRecipeLoader.java b/src/main/java/gregtech/loaders/recipe/MetaTileEntityMachineRecipeLoader.java
index cc01014c03a..39e12fe8af3 100644
--- a/src/main/java/gregtech/loaders/recipe/MetaTileEntityMachineRecipeLoader.java
+++ b/src/main/java/gregtech/loaders/recipe/MetaTileEntityMachineRecipeLoader.java
@@ -1,14 +1,18 @@
 package gregtech.loaders.recipe;
 
 import gregtech.api.GTValues;
+import gregtech.api.items.OreDictNames;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.recipes.ModHandler;
+import gregtech.api.recipes.ingredients.GTRecipeInput;
+import gregtech.api.recipes.ingredients.GTRecipeItemInput;
+import gregtech.api.recipes.ingredients.GTRecipeOreInput;
 import gregtech.api.unification.stack.UnificationEntry;
+import gregtech.api.util.GTUtility;
+import gregtech.api.util.Mods;
 
 import net.minecraft.init.Blocks;
 import net.minecraft.item.ItemStack;
-import net.minecraftforge.fml.common.Loader;
-import net.minecraftforge.fml.common.registry.GameRegistry;
 
 import static gregtech.api.GTValues.*;
 import static gregtech.api.recipes.RecipeMaps.ASSEMBLER_RECIPES;
@@ -19,7 +23,6 @@
 import static gregtech.common.blocks.MetaBlocks.LD_FLUID_PIPE;
 import static gregtech.common.blocks.MetaBlocks.LD_ITEM_PIPE;
 import static gregtech.common.items.MetaItems.*;
-import static gregtech.common.items.MetaItems.SENSOR_UV;
 import static gregtech.common.metatileentities.MetaTileEntities.*;
 
 public class MetaTileEntityMachineRecipeLoader {
@@ -243,8 +246,8 @@ public static void init() {
                 .duration(300).EUt(VA[EV]).buildAndRegister();
 
         // Item Buses
-        registerHatchBusRecipe(ULV, ITEM_IMPORT_BUS[ULV], ITEM_EXPORT_BUS[ULV], new ItemStack(Blocks.CHEST));
-        registerHatchBusRecipe(LV, ITEM_IMPORT_BUS[LV], ITEM_EXPORT_BUS[LV], new ItemStack(Blocks.CHEST));
+        registerHatchBusRecipe(ULV, ITEM_IMPORT_BUS[ULV], ITEM_EXPORT_BUS[ULV], OreDictNames.chestWood.toString());
+        registerHatchBusRecipe(LV, ITEM_IMPORT_BUS[LV], ITEM_EXPORT_BUS[LV], OreDictNames.chestWood.toString());
         registerHatchBusRecipe(MV, ITEM_IMPORT_BUS[MV], ITEM_EXPORT_BUS[MV], BRONZE_CRATE.getStackForm());
         registerHatchBusRecipe(HV, ITEM_IMPORT_BUS[HV], ITEM_EXPORT_BUS[HV], STEEL_CRATE.getStackForm());
         registerHatchBusRecipe(EV, ITEM_IMPORT_BUS[EV], ITEM_EXPORT_BUS[EV], ALUMINIUM_CRATE.getStackForm());
@@ -989,11 +992,11 @@ public static void init() {
 
         // ME Parts
 
-        if (Loader.isModLoaded(MODID_APPENG)) {
+        if (Mods.AppliedEnergistics2.isModLoaded()) {
 
-            ItemStack fluidInterface = GameRegistry.makeItemStack(MODID_APPENG + ":fluid_interface", 0, 1, null);
-            ItemStack normalInterface = GameRegistry.makeItemStack(MODID_APPENG + ":interface", 0, 1, null);
-            ItemStack accelerationCard = GameRegistry.makeItemStack(MODID_APPENG + ":material", 30, 2, null);
+            ItemStack fluidInterface = Mods.AppliedEnergistics2.getItem("fluid_interface");
+            ItemStack normalInterface = Mods.AppliedEnergistics2.getItem("interface");
+            ItemStack accelerationCard = Mods.AppliedEnergistics2.getItem("material", 30, 2);
 
             ASSEMBLER_RECIPES.recipeBuilder()
                     .input(FLUID_EXPORT_HATCH[EV])
@@ -1022,11 +1025,38 @@ public static void init() {
                     .inputs(accelerationCard.copy())
                     .output(ITEM_IMPORT_BUS_ME)
                     .duration(300).EUt(VA[HV]).buildAndRegister();
+
+            ASSEMBLER_RECIPES.recipeBuilder()
+                    .input(ITEM_IMPORT_BUS[IV])
+                    .inputs(normalInterface.copy())
+                    .input(CONVEYOR_MODULE_IV)
+                    .input(SENSOR_IV)
+                    .inputs(GTUtility.copy(4, accelerationCard))
+                    .output(STOCKING_BUS_ME)
+                    .duration(300).EUt(VA[IV]).buildAndRegister();
+
+            ASSEMBLER_RECIPES.recipeBuilder()
+                    .input(FLUID_IMPORT_HATCH[IV])
+                    .inputs(fluidInterface.copy())
+                    .input(ELECTRIC_PUMP_IV)
+                    .input(SENSOR_IV)
+                    .inputs(GTUtility.copy(4, accelerationCard))
+                    .output(STOCKING_HATCH_ME)
+                    .duration(300).EUt(VA[IV]).buildAndRegister();
         }
     }
 
     private static void registerHatchBusRecipe(int tier, MetaTileEntity inputBus, MetaTileEntity outputBus,
-                                               ItemStack extra) {
+                                               Object extraInput) {
+        GTRecipeInput extra;
+        if (extraInput instanceof ItemStack stack) {
+            extra = new GTRecipeItemInput(stack);
+        } else if (extraInput instanceof String oreName) {
+            extra = new GTRecipeOreInput(oreName);
+        } else {
+            throw new IllegalArgumentException("extraInput must be ItemStack or GTRecipeInput");
+        }
+
         // Glue recipe for ULV and LV
         // 250L for ULV, 500L for LV
         if (tier <= GTValues.LV) {
@@ -1115,29 +1145,18 @@ private static void registerHatchBusRecipe(int tier, MetaTileEntity inputBus, Me
     }
 
     private static int getFluidAmount(int offsetTier) {
-        switch (offsetTier) {
-            case 0:
-                return 4;
-            case 1:
-                return 9;
-            case 2:
-                return 18;
-            case 3:
-                return 36;
-            case 4:
-                return 72;
-            case 5:
-                return 144;
-            case 6:
-                return 288;
-            case 7:
-                return 432;
-            case 8:
-                return 576;
-            case 9:
-            default:
-                return 720;
-        }
+        return switch (offsetTier) {
+            case 0 -> 4;
+            case 1 -> 9;
+            case 2 -> 18;
+            case 3 -> 36;
+            case 4 -> 72;
+            case 5 -> 144;
+            case 6 -> 288;
+            case 7 -> 432;
+            case 8 -> 576;
+            default -> 720;
+        };
     }
 
     // TODO clean this up with a CraftingComponent rework
diff --git a/src/main/java/gregtech/loaders/recipe/RecyclingRecipes.java b/src/main/java/gregtech/loaders/recipe/RecyclingRecipes.java
index e4cbbab3310..ba4223d289f 100644
--- a/src/main/java/gregtech/loaders/recipe/RecyclingRecipes.java
+++ b/src/main/java/gregtech/loaders/recipe/RecyclingRecipes.java
@@ -140,7 +140,10 @@ private static void registerExtractorRecycling(ItemStack input, List ms.material.hasProperty(PropertyKey.FLUID)).findFirst()
-                .orElse(null);
+        MaterialStack fluidMs = materials.stream()
+                .filter(ms -> ms.material.hasProperty(PropertyKey.FLUID) && ms.material.getFluid() != null)
+                .findFirst().orElse(null);
         if (fluidMs == null) return;
 
         // Find the next MaterialStack, which will be the Item output.
diff --git a/src/main/java/gregtech/loaders/recipe/WoodRecipeLoader.java b/src/main/java/gregtech/loaders/recipe/WoodRecipeLoader.java
index fb06348d385..c790b6c809d 100644
--- a/src/main/java/gregtech/loaders/recipe/WoodRecipeLoader.java
+++ b/src/main/java/gregtech/loaders/recipe/WoodRecipeLoader.java
@@ -482,7 +482,7 @@ private static void registerGTWoodRecipes() {
                 'L', MetaBlocks.PLANKS.getItemVariant(BlockGregPlanks.BlockType.TREATED_PLANK));
         if (ConfigHolder.recipes.nerfWoodCrafting) {
             ModHandler.addShapedRecipe("treated_wood_stick_saw", OreDictUnifier.get(OrePrefix.stick, TreatedWood, 4),
-                    "s", "L",
+                    "s", "L", "L",
                     'L', MetaBlocks.PLANKS.getItemVariant(BlockGregPlanks.BlockType.TREATED_PLANK));
         }
     }
diff --git a/src/main/java/gregtech/loaders/recipe/chemistry/ReactorRecipes.java b/src/main/java/gregtech/loaders/recipe/chemistry/ReactorRecipes.java
index 7871a518e8c..ebe5ed15505 100644
--- a/src/main/java/gregtech/loaders/recipe/chemistry/ReactorRecipes.java
+++ b/src/main/java/gregtech/loaders/recipe/chemistry/ReactorRecipes.java
@@ -3,6 +3,7 @@
 import gregtech.api.unification.OreDictUnifier;
 import gregtech.api.unification.material.MarkerMaterials;
 import gregtech.api.unification.material.Materials;
+import gregtech.common.blocks.MetaBlocks;
 import gregtech.common.items.MetaItems;
 
 import net.minecraft.init.Blocks;
@@ -570,6 +571,13 @@ public static void init() {
                 .outputs(new ItemStack(Blocks.TNT))
                 .duration(200).EUt(24).buildAndRegister();
 
+        CHEMICAL_RECIPES.recipeBuilder()
+                .inputs(MetaItems.GELLED_TOLUENE.getStackForm(4))
+                .fluidInputs(NitrationMixture.getFluid(200))
+                .outputs(new ItemStack(MetaBlocks.ITNT))
+                .fluidOutputs(DilutedSulfuricAcid.getFluid(150))
+                .duration(80).EUt(VA[HV]).buildAndRegister();
+
         CHEMICAL_RECIPES.recipeBuilder()
                 .input(dust, SodiumHydroxide, 6)
                 .fluidInputs(Dichlorobenzene.getFluid(1000))
diff --git a/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java b/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java
index 3d51fc11124..fc433308a0d 100644
--- a/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java
+++ b/src/main/java/gregtech/loaders/recipe/chemistry/SeparationRecipes.java
@@ -369,44 +369,6 @@ public static void init() {
                 .output(dust, Carbon, 4)
                 .duration(100).EUt(60).buildAndRegister();
 
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(AceticAcid.getFluid(2000))
-                .fluidOutputs(Ethane.getFluid(1000))
-                .fluidOutputs(CarbonDioxide.getFluid(2000))
-                .fluidOutputs(Hydrogen.getFluid(2000))
-                .duration(512).EUt(60).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Chloromethane.getFluid(2000))
-                .fluidOutputs(Ethane.getFluid(1000))
-                .fluidOutputs(Chlorine.getFluid(2000))
-                .duration(400).EUt(60).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Acetone.getFluid(2000))
-                .output(dust, Carbon, 3)
-                .fluidOutputs(Propane.getFluid(1000))
-                .fluidOutputs(Water.getFluid(2000))
-                .duration(480).EUt(60).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Butane.getFluid(1000))
-                .fluidOutputs(Butene.getFluid(1000))
-                .fluidOutputs(Hydrogen.getFluid(2000))
-                .duration(240).EUt(VA[MV]).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Butene.getFluid(1000))
-                .fluidOutputs(Butadiene.getFluid(1000))
-                .fluidOutputs(Hydrogen.getFluid(2000))
-                .duration(240).EUt(VA[MV]).buildAndRegister();
-
-        ELECTROLYZER_RECIPES.recipeBuilder()
-                .fluidInputs(Propane.getFluid(1000))
-                .fluidOutputs(Propene.getFluid(1000))
-                .fluidOutputs(Hydrogen.getFluid(2000))
-                .duration(640).EUt(VA[MV]).buildAndRegister();
-
         ELECTROLYZER_RECIPES.recipeBuilder()
                 .input(dust, Diamond)
                 .output(dust, Carbon, 64)
@@ -470,6 +432,92 @@ public static void init() {
                 .fluidOutputs(Chlorine.getFluid(1000))
                 .duration(288).EUt(60).buildAndRegister();
 
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Propane.getFluid(1000))
+                .output(dust, Carbon, 3)
+                .fluidOutputs(Hydrogen.getFluid(8000))
+                .duration(176).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Butene.getFluid(1000))
+                .output(dust, Carbon, 4)
+                .fluidOutputs(Hydrogen.getFluid(8000))
+                .duration(192).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Butane.getFluid(1000))
+                .output(dust, Carbon, 4)
+                .fluidOutputs(Hydrogen.getFluid(10000))
+                .duration(224).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Styrene.getFluid(1000))
+                .output(dust, Carbon, 8)
+                .fluidOutputs(Hydrogen.getFluid(8000))
+                .duration(384).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Butadiene.getFluid(1000))
+                .output(dust, Carbon, 4)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .duration(240).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Phenol.getFluid(1000))
+                .output(dust, Carbon, 6)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .fluidOutputs(Oxygen.getFluid(1000))
+                .duration(312).EUt(90).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Ethylene.getFluid(1000))
+                .output(dust, Carbon, 2)
+                .fluidOutputs(Hydrogen.getFluid(4000))
+                .duration(96).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Benzene.getFluid(1000))
+                .output(dust, Carbon, 6)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .duration(288).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Ethanol.getFluid(1000))
+                .output(dust, Carbon, 2)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .fluidOutputs(Oxygen.getFluid(1000))
+                .duration(144).EUt(90).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Toluene.getFluid(1000))
+                .output(dust, Carbon, 7)
+                .fluidOutputs(Hydrogen.getFluid(8000))
+                .duration(360).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Dimethylbenzene.getFluid(1000))
+                .output(dust, Carbon, 8)
+                .fluidOutputs(Hydrogen.getFluid(10000))
+                .duration(432).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Octane.getFluid(1000))
+                .output(dust, Carbon, 8)
+                .fluidOutputs(Hydrogen.getFluid(18000))
+                .duration(624).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Propene.getFluid(1000))
+                .output(dust, Carbon, 3)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .duration(144).EUt(60).buildAndRegister();
+
+        ELECTROLYZER_RECIPES.recipeBuilder()
+                .fluidInputs(Ethane.getFluid(1000))
+                .output(dust, Carbon, 2)
+                .fluidOutputs(Hydrogen.getFluid(6000))
+                .duration(128).EUt(60).buildAndRegister();
+
         // Thermal Centrifuge
         THERMAL_CENTRIFUGE_RECIPES.recipeBuilder()
                 .inputs(new ItemStack(Blocks.COBBLESTONE, 1, GTValues.W))
diff --git a/src/main/java/gregtech/loaders/recipe/handlers/MaterialRecipeHandler.java b/src/main/java/gregtech/loaders/recipe/handlers/MaterialRecipeHandler.java
index ac915b45dc7..cbdf94add16 100644
--- a/src/main/java/gregtech/loaders/recipe/handlers/MaterialRecipeHandler.java
+++ b/src/main/java/gregtech/loaders/recipe/handlers/MaterialRecipeHandler.java
@@ -13,6 +13,7 @@
 import gregtech.api.unification.stack.UnificationEntry;
 import gregtech.api.util.GTUtility;
 import gregtech.common.ConfigHolder;
+import gregtech.common.blocks.MetaBlocks;
 import gregtech.common.items.MetaItems;
 import gregtech.loaders.recipe.CraftingComponent;
 
@@ -90,14 +91,28 @@ public static void processDust(OrePrefix dustPrefix, Material mat, DustProperty
                         .inputs(GTUtility.copy(4, dustStack))
                         .outputs(GTUtility.copy(3, gemStack))
                         .chancedOutput(dust, Materials.DarkAsh, 2500, 0)
-                        .explosivesAmount(2)
+                        .explosivesType(new ItemStack(MetaBlocks.POWDERBARREL, 8))
                         .buildAndRegister();
 
                 RecipeMaps.IMPLOSION_RECIPES.recipeBuilder()
                         .inputs(GTUtility.copy(4, dustStack))
                         .outputs(GTUtility.copy(3, gemStack))
                         .chancedOutput(dust, Materials.DarkAsh, 2500, 0)
-                        .explosivesType(MetaItems.DYNAMITE.getStackForm())
+                        .explosivesAmount(4)
+                        .buildAndRegister();
+
+                RecipeMaps.IMPLOSION_RECIPES.recipeBuilder()
+                        .inputs(GTUtility.copy(4, dustStack))
+                        .outputs(GTUtility.copy(3, gemStack))
+                        .chancedOutput(dust, Materials.DarkAsh, 2500, 0)
+                        .explosivesType(MetaItems.DYNAMITE.getStackForm(2))
+                        .buildAndRegister();
+
+                RecipeMaps.IMPLOSION_RECIPES.recipeBuilder()
+                        .inputs(GTUtility.copy(4, dustStack))
+                        .outputs(GTUtility.copy(3, gemStack))
+                        .chancedOutput(dust, Materials.DarkAsh, 2500, 0)
+                        .explosivesType(new ItemStack(MetaBlocks.ITNT))
                         .buildAndRegister();
             }
 
@@ -286,10 +301,10 @@ public static void processIngot(OrePrefix ingotPrefix, Material material, IngotP
             }
         }
 
-        if (material.hasFluid()) {
+        if (material.hasFluid() && material.getProperty(PropertyKey.FLUID).solidifiesFrom() != null) {
             RecipeMaps.FLUID_SOLIDFICATION_RECIPES.recipeBuilder()
                     .notConsumable(MetaItems.SHAPE_MOLD_INGOT)
-                    .fluidInputs(material.getFluid(L))
+                    .fluidInputs(material.getProperty(PropertyKey.FLUID).solidifiesFrom(L))
                     .outputs(OreDictUnifier.get(ingotPrefix, material))
                     .duration(20).EUt(VA[ULV])
                     .buildAndRegister();
@@ -424,10 +439,10 @@ public static void processNugget(OrePrefix orePrefix, Material material, DustPro
                     .output(ingot, material)
                     .buildAndRegister();
 
-            if (material.hasFluid()) {
+            if (material.hasFluid() && material.getProperty(PropertyKey.FLUID).solidifiesFrom() != null) {
                 RecipeMaps.FLUID_SOLIDFICATION_RECIPES.recipeBuilder()
                         .notConsumable(MetaItems.SHAPE_MOLD_NUGGET)
-                        .fluidInputs(material.getFluid(L))
+                        .fluidInputs(material.getProperty(PropertyKey.FLUID).solidifiesFrom(L))
                         .outputs(OreDictUnifier.get(orePrefix, material, 9))
                         .duration((int) material.getMass())
                         .EUt(VA[ULV])
@@ -465,10 +480,11 @@ public static void processFrame(OrePrefix framePrefix, Material material, DustPr
     public static void processBlock(OrePrefix blockPrefix, Material material, DustProperty property) {
         ItemStack blockStack = OreDictUnifier.get(blockPrefix, material);
         long materialAmount = blockPrefix.getMaterialAmount(material);
-        if (material.hasFluid()) {
+        if (material.hasFluid() && material.getProperty(PropertyKey.FLUID).solidifiesFrom() != null) {
             RecipeMaps.FLUID_SOLIDFICATION_RECIPES.recipeBuilder()
                     .notConsumable(MetaItems.SHAPE_MOLD_BLOCK)
-                    .fluidInputs(material.getFluid((int) (materialAmount * L / M)))
+                    .fluidInputs(material.getProperty(PropertyKey.FLUID).solidifiesFrom(
+                            ((int) (materialAmount * L / M))))
                     .outputs(blockStack)
                     .duration((int) material.getMass()).EUt(VA[ULV])
                     .buildAndRegister();
diff --git a/src/main/java/gregtech/loaders/recipe/handlers/PartsRecipeHandler.java b/src/main/java/gregtech/loaders/recipe/handlers/PartsRecipeHandler.java
index 24ec5349133..ddff63fd34a 100644
--- a/src/main/java/gregtech/loaders/recipe/handlers/PartsRecipeHandler.java
+++ b/src/main/java/gregtech/loaders/recipe/handlers/PartsRecipeHandler.java
@@ -200,11 +200,12 @@ public static void processGear(OrePrefix gearPrefix, Material material, DustProp
             }
         }
 
-        if (material.hasFluid()) {
+        if (material.hasFluid() && material.getProperty(PropertyKey.FLUID).solidifiesFrom() != null) {
             boolean isSmall = gearPrefix == OrePrefix.gearSmall;
             RecipeMaps.FLUID_SOLIDFICATION_RECIPES.recipeBuilder()
                     .notConsumable(isSmall ? MetaItems.SHAPE_MOLD_GEAR_SMALL : MetaItems.SHAPE_MOLD_GEAR)
-                    .fluidInputs(material.getFluid(L * (isSmall ? 1 : 4)))
+                    .fluidInputs(
+                            material.getProperty(PropertyKey.FLUID).solidifiesFrom(L * (isSmall ? 1 : 4)))
                     .outputs(stack)
                     .duration(isSmall ? 20 : 100)
                     .EUt(VA[ULV])
@@ -285,10 +286,10 @@ public static void processLens(OrePrefix lensPrefix, Material material, GemPrope
     }
 
     public static void processPlate(OrePrefix platePrefix, Material material, DustProperty property) {
-        if (material.hasFluid()) {
+        if (material.hasFluid() && material.getProperty(PropertyKey.FLUID).solidifiesFrom() != null) {
             RecipeMaps.FLUID_SOLIDFICATION_RECIPES.recipeBuilder()
                     .notConsumable(MetaItems.SHAPE_MOLD_PLATE)
-                    .fluidInputs(material.getFluid(L))
+                    .fluidInputs(material.getProperty(PropertyKey.FLUID).solidifiesFrom(L))
                     .outputs(OreDictUnifier.get(platePrefix, material))
                     .duration(40)
                     .EUt(VA[ULV])
@@ -399,10 +400,10 @@ public static void processRotor(OrePrefix rotorPrefix, Material material, IngotP
                 'S', new UnificationEntry(screw, material),
                 'R', new UnificationEntry(ring, material));
 
-        if (material.hasFluid()) {
+        if (material.hasFluid() && material.getProperty(PropertyKey.FLUID).solidifiesFrom() != null) {
             RecipeMaps.FLUID_SOLIDFICATION_RECIPES.recipeBuilder()
                     .notConsumable(MetaItems.SHAPE_MOLD_ROTOR)
-                    .fluidInputs(material.getFluid(L * 4))
+                    .fluidInputs(material.getProperty(PropertyKey.FLUID).solidifiesFrom(L * 4))
                     .outputs(GTUtility.copy(stack))
                     .duration(120)
                     .EUt(20)
diff --git a/src/main/java/gregtech/loaders/recipe/handlers/ToolRecipeHandler.java b/src/main/java/gregtech/loaders/recipe/handlers/ToolRecipeHandler.java
index 4b6ec9210b2..1997631a38c 100644
--- a/src/main/java/gregtech/loaders/recipe/handlers/ToolRecipeHandler.java
+++ b/src/main/java/gregtech/loaders/recipe/handlers/ToolRecipeHandler.java
@@ -287,6 +287,10 @@ private static void processElectricTool(OrePrefix prefix, Material material, Too
                         .EUt(8 * voltageMultiplier)
                         .buildAndRegister();
             }
+
+            // wirecutter
+            addElectricWirecutterRecipe(material,
+                    new IGTTool[] { ToolItems.WIRECUTTER_LV, ToolItems.WIRECUTTER_HV, ToolItems.WIRECUTTER_IV });
         }
 
         // screwdriver
@@ -316,6 +320,21 @@ public static void addElectricToolRecipe(OrePrefix toolHead, Material material,
         }
     }
 
+    public static void addElectricWirecutterRecipe(Material material, IGTTool[] toolItems) {
+        for (IGTTool toolItem : toolItems) {
+            int tier = toolItem.getElectricTier();
+            ItemStack powerUnitStack = powerUnitItems.get(tier).getStackForm();
+            IElectricItem powerUnit = powerUnitStack.getCapability(GregtechCapabilities.CAPABILITY_ELECTRIC_ITEM, null);
+            ItemStack tool = toolItem.get(material, 0, powerUnit.getMaxCharge());
+            ModHandler.addShapedEnergyTransferRecipe(String.format("%s_%s", toolItem.getToolId(), material), tool,
+                    Ingredient.fromStacks(powerUnitStack), true, true,
+                    "PfP", "hPd", "RUR",
+                    'P', new UnificationEntry(OrePrefix.plate, material),
+                    'U', powerUnitStack,
+                    'R', new UnificationEntry(OrePrefix.stick, material));
+        }
+    }
+
     public static void addToolRecipe(@NotNull Material material, @NotNull IGTTool tool, boolean mirrored,
                                      Object... recipe) {
         if (mirrored) {
diff --git a/src/main/java/gregtech/modules/GregTechModules.java b/src/main/java/gregtech/modules/GregTechModules.java
index 9c07e90db26..96ad33664b9 100644
--- a/src/main/java/gregtech/modules/GregTechModules.java
+++ b/src/main/java/gregtech/modules/GregTechModules.java
@@ -17,6 +17,7 @@ public class GregTechModules implements IModuleContainer {
     public static final String MODULE_HWYLA = "hwyla_integration";
     public static final String MODULE_BAUBLES = "baubles_integration";
     public static final String MODULE_FR = "fr_integration";
+    public static final String MODULE_CHISEL = "chisel_integration";
 
     @Override
     public String getID() {
diff --git a/src/main/resources/assets/gregtech/blockstates/itnt.json b/src/main/resources/assets/gregtech/blockstates/itnt.json
new file mode 100644
index 00000000000..79c7d216ccb
--- /dev/null
+++ b/src/main/resources/assets/gregtech/blockstates/itnt.json
@@ -0,0 +1,16 @@
+{
+  "forge_marker": 1,
+  "defaults": {
+    "model": "minecraft:cube_bottom_top",
+    "textures": {
+      "bottom": "minecraft:blocks/tnt_bottom",
+      "top": "minecraft:blocks/tnt_top",
+      "side": "gregtech:blocks/misc/itnt"
+    }
+  },
+  "variants": {
+    "normal": [
+      {}
+    ]
+  }
+}
diff --git a/src/main/resources/assets/gregtech/blockstates/powderbarrel.json b/src/main/resources/assets/gregtech/blockstates/powderbarrel.json
new file mode 100644
index 00000000000..60d676619f5
--- /dev/null
+++ b/src/main/resources/assets/gregtech/blockstates/powderbarrel.json
@@ -0,0 +1,14 @@
+{
+  "forge_marker": 1,
+  "defaults": {
+    "model": "minecraft:cube_all",
+    "textures": {
+      "all": "gregtech:blocks/misc/powderbarrel"
+    }
+  },
+  "variants": {
+    "normal": [
+      {}
+    ]
+  }
+}
diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang
index f34849c4bdd..5d1adf35389 100644
--- a/src/main/resources/assets/gregtech/lang/en_us.lang
+++ b/src/main/resources/assets/gregtech/lang/en_us.lang
@@ -531,7 +531,7 @@ metaitem.tool.datamodule.name=Data Module
 metaitem.tool.datamodule.tooltip=Storage for Incredibly Complex Data/n§cCan only be read by a Data Bank
 metaitem.circuit.integrated.name=Programmed Circuit
 metaitem.circuit.integrated.tooltip=Use to open configuration GUI/n/nShift-Right-Click on a machine/nwith a circuit slot to set it to/nthis circuit's value./n
-metaitem.circuit.integrated.gui=Programmed Circuit Configuration
+metaitem.circuit.integrated.gui=Circuit Configuration
 metaitem.circuit.integrated.jei_description=JEI is only showing recipes for the given configuration.\n\nYou can select a configuration in the Programmed Circuit configuration tab.
 
 item.glass.lens=Glass Lens (White)
@@ -1008,6 +1008,9 @@ item.gt.tool.screwdriver_lv.name=%s Screwdriver (LV)
 item.gt.tool.screwdriver_lv.tooltip=§8Adjusts Covers and Machines
 item.gt.tool.plunger.name=%s Plunger
 item.gt.tool.plunger.tooltip=§8Removes Fluids from Machines
+item.gt.tool.wire_cutter_lv.name=%s Wire Cutter (LV)
+item.gt.tool.wire_cutter_hv.name=%s Wire Cutter (HV)
+item.gt.tool.wire_cutter_iv.name=%s Wire Cutter (IV)
 
 
 item.gt.tool.tooltip.crafting_uses=§a%s Crafting Uses
@@ -1175,7 +1178,7 @@ cover.filter.blacklist.disabled=Whitelist
 cover.filter.blacklist.enabled=Blacklist
 
 cover.ore_dictionary_filter.title=Ore Dictionary Filter
-cover.ore_dictionary_filter.info=§bAccepts complex expressions/n§6a & b§r = AND/n§6a | b§r = OR/n§6a ^ b§r = XOR/n§6! abc§r = NOT/n§6( abc )§r for grouping/n§6*§r for wildcard (i.e. 0 or more characters)/n§6?§r for any 1 character/n§6()§r for matching empty entry (including items with no ore dictionary)/n§6$c§r at the start of expression for case-sensitive match/n§bExample:/n§6dust*Gold | (plate* & !*Double*)/nWill match all gold dusts of all sizes or all plates, but not double plates
+cover.ore_dictionary_filter.info=§bAccepts complex expressions/n§6a & b§r = AND/n§6a | b§r = OR/n§6a ^ b§r = XOR/n§6! abc§r = NOT/n§6( abc )§r for grouping/n§6*§r for wildcard (i.e. 0 or more characters)/n§6?§r for any 1 character/n§6()§r for matching empty entry (including items with no ore dictionary)/n§bExample:/n§6dust*Gold | (plate* & !*Double*)/nWill match all gold dusts of all sizes or all plates, but not double plates
 cover.ore_dictionary_filter.test_slot.info=Insert a item to test if it matches the filter expression
 cover.ore_dictionary_filter.test_slot.matches=§a* %s
 cover.ore_dictionary_filter.test_slot.matches_not=§c* %s
@@ -1186,6 +1189,10 @@ cover.ore_dictionary_filter.status.err_warn=§c%s error(s) and %s warning(s)
 cover.ore_dictionary_filter.status.warn=§7%s warning(s)
 cover.ore_dictionary_filter.status.no_issues=§aNo issues
 cover.ore_dictionary_filter.status.explain=Ore filter explanation:
+cover.ore_dictionary_filter.button.case_sensitive.disabled=Case insensitive match
+cover.ore_dictionary_filter.button.case_sensitive.enabled=Case sensitive match
+cover.ore_dictionary_filter.button.match_all.disabled=Match any one of ore dictionary entries
+cover.ore_dictionary_filter.button.match_all.enabled=Match all ore dictionary entries
 
 cover.ore_dictionary_filter.preview.next=... followed by 
 cover.ore_dictionary_filter.preview.match='%s'
@@ -1300,11 +1307,6 @@ cover.fluid_regulator.transfer_mode.description=§eTransfer Any§r - in this mod
 cover.fluid_regulator.supply_exact=Supply Exact: %s
 cover.fluid_regulator.keep_exact=Keep Exact: %s
 
-cover.machine_controller.title=Machine Controller Settings
-cover.machine_controller.normal=Normal
-cover.machine_controller.inverted=Inverted
-cover.machine_controller.inverted.description=§eNormal§r - in this mode, the cover will require a signal weaker than the set redstone level to run/n§eInverted§r - in this mode, the cover will require a signal stronger than the set redstone level to run
-cover.machine_controller.redstone=Min Redstone Strength: %,d
 cover.machine_controller.mode.machine=Control Machine
 cover.machine_controller.mode.cover_up=Control Cover (Top)
 cover.machine_controller.mode.cover_down=Control Cover (Bottom)
@@ -1312,6 +1314,12 @@ cover.machine_controller.mode.cover_south=Control Cover (South)
 cover.machine_controller.mode.cover_north=Control Cover (North)
 cover.machine_controller.mode.cover_east=Control Cover (East)
 cover.machine_controller.mode.cover_west=Control Cover (West)
+cover.machine_controller.this_cover=§cThis cover
+cover.machine_controller.cover_not_controllable=§cNo controllable cover
+cover.machine_controller.machine_not_controllable=§cMachine not controllable
+cover.machine_controller.control=Control:
+cover.machine_controller.enable_with_redstone=Enable with Redstone
+cover.machine_controller.disable_with_redstone=Disable with Redstone
 
 cover.ender_fluid_link.title=Ender Fluid Link
 cover.ender_fluid_link.iomode.enabled=I/O Enabled
@@ -2343,6 +2351,17 @@ tile.treated_wood_fence.name=Treated Wood Fence
 tile.rubber_wood_fence_gate.name=Rubber Wood Fence Gate
 tile.treated_wood_fence_gate.name=Treated Wood Fence Gate
 
+tile.gt_explosive.breaking_tooltip=Primes explosion when mined, sneak mine to pick back up
+tile.gt_explosive.lighting_tooltip=Cannot be lit with Redstone
+
+tile.powderbarrel.name=Powderbarrel
+tile.powderbarrel.drops_tooltip=Slightly larger than TNT, drops all destroyed Blocks as Items
+entity.Powderbarrel.name=Powderbarrel
+
+tile.itnt.name=Industrial TNT
+tile.itnt.drops_tooltip=Much larger than TNT, drops all destroyed Blocks as Items
+entity.ITNT.name=Industrial TNT
+
 tile.brittle_charcoal.name=Brittle Charcoal
 tile.brittle_charcoal.tooltip.1=Produced by the Charcoal Pile Igniter.
 tile.brittle_charcoal.tooltip.2=Mine this to get Charcoal.
@@ -5238,15 +5257,32 @@ gregtech.machine.laser_hatch.tooltip2=§cLaser Cables must be in a straight line
 gregtech.machine.fluid_tank.max_multiblock=Max Multiblock Size: %,dx%,dx%,d
 gregtech.machine.fluid_tank.fluid=Contains %s L of %s
 
-gregtech.machine.me_export_fluid_hatch.name=ME Output Hatch
+# ME Parts
+gregtech.machine.me_import_item_bus.name=ME Input Bus
+gregtech.machine.me.item_import.tooltip=Extracts specified items from the ME network
+gregtech.machine.me_stocking_item_bus.name=ME Stocking Input Bus
+gregtech.machine.me.stocking_item.tooltip=Retrieves items directly from the ME network
+gregtech.machine.me.stocking_item.tooltip.2=Auto-Pull from ME mode will automatically stock the first 16 items in the ME system, updated every 5 seconds.
+gregtech.machine.me_import_item_hatch.configs.tooltip=Keeps 16 item types in stock
+gregtech.machine.me_import_fluid_hatch.name=ME Input Hatch
+gregtech.machine.me.fluid_import.tooltip=Extracts specified fluids from ME network
+gregtech.machine.me_stocking_fluid_hatch.name=ME Stocking Input Hatch
+gregtech.machine.me.stocking_fluid.tooltip=Retrieves fluids directly from the ME network
+gregtech.machine.me.stocking_fluid.tooltip.2=Auto-Pull from ME mode will automatically stock the first 16 fluids in the ME system, updated every 5 seconds.
+gregtech.machine.me_import_fluid_hatch.configs.tooltip=Keeps 16 fluid types in stock
 gregtech.machine.me_export_item_bus.name=ME Output Bus
-gregtech.machine.me_import_fluid_hatch.name=ME Stocking Input Hatch
-gregtech.machine.me_import_item_bus.name=ME Stocking Input Bus
-gregtech.machine.me.fluid_export.tooltip=Stores fluid directly into ME network.
-gregtech.machine.me.item_export.tooltip=Stores item directly into ME network.
-gregtech.machine.me.fluid_import.tooltip=Fetch fluids from ME network automatically.
-gregtech.machine.me.item_import.tooltip=Fetch items from ME network automatically.
-gregtech.machine.me.export.tooltip=It has infinite capacity before connecting to ME network.
+gregtech.machine.me.item_export.tooltip=Stores items directly into the ME network
+gregtech.machine.me.item_export.tooltip.2=Can cache an infinite amount of items
+gregtech.machine.me_export_fluid_hatch.name=ME Output Hatch
+gregtech.machine.me.fluid_export.tooltip=Stores fluids directly into the ME network
+gregtech.machine.me.fluid_export.tooltip.2=Can cache an infinite amount of fluid
+gregtech.machine.me.stocking_auto_pull_enabled=Auto-Pull Enabled
+gregtech.machine.me.stocking_auto_pull_disabled=Auto-Pull Disabled
+gregtech.machine.me.copy_paste.tooltip=Left-click with Data Stick to copy settings, right-click to apply
+gregtech.machine.me.import_copy_settings=Saved settings to Data Stick
+gregtech.machine.me.import_paste_settings=Applied settings from Data Stick
+gregtech.machine.me.item_import.data_stick.name=§oME Input Bus Configuration Data
+gregtech.machine.me.fluid_import.data_stick.name=§oME Input Hatch Configuration Data
 
 # Universal tooltips
 gregtech.universal.tooltip.voltage_in=§aVoltage IN: §f%,d EU/t (%s§f)
@@ -5306,6 +5342,7 @@ gregtech.recipe.temperature=Temperature: %,dK (%s)
 gregtech.recipe.explosive=Explosive: %s
 gregtech.recipe.eu_to_start=Energy To Start: %sEU
 gregtech.recipe.dimensions=Dimensions: %s
+gregtech.recipe.dimensions_blocked=Blocked Dimensions: %s
 gregtech.recipe.cleanroom=Requires %s
 gregtech.recipe.cleanroom.display_name=Cleanroom
 gregtech.recipe.cleanroom_sterile.display_name=Sterile Cleanroom
@@ -5387,8 +5424,12 @@ gregtech.gui.me_network.offline=Network Status: §4Offline§r
 gregtech.gui.waiting_list=Sending Queue:
 gregtech.gui.config_slot=§fConfig Slot§r
 gregtech.gui.config_slot.set=§7Click to §bset/select§7 config slot.§r
+gregtech.gui.config_slot.set_only=§7Click to §bset§7 config slot.§r
 gregtech.gui.config_slot.scroll=§7Scroll wheel to §achange§7 config amount.§r
 gregtech.gui.config_slot.remove=§7Right click to §4clear§7 config slot.§r
+gregtech.gui.config_slot.auto_pull_managed=§4Disabled:§7 Managed by Auto-Pull
+gregtech.gui.me_bus.extra_slot=Extra Slot/n§7Put extra items for recipes here, like Molds or Lenses
+gregtech.gui.me_bus.auto_pull_button=Click to toggle automatic item pulling from ME
 gregtech.gui.alarm.radius=Radius:
 
 
@@ -5557,7 +5598,7 @@ gregtech.multiblock.pattern.error.batteries=§cMust have at least one non-empty
 gregtech.multiblock.pattern.error.filters=§cAll filters must be the same§r
 gregtech.multiblock.pattern.clear_amount_1=§6Must have a clear 1x1x1 space in front§r
 gregtech.multiblock.pattern.clear_amount_3=§6Must have a clear 3x3x1 space in front§r
-gregtech.multiblock.pattern.single=§6Only this block can be used§r
+gregtech.multiblock.pattern.single=§6Only this block type can be used§r
 gregtech.multiblock.pattern.location_end=§cVery End§r
 gregtech.multiblock.pattern.replaceable_air=Replaceable by Air
 
@@ -5642,8 +5683,10 @@ gregtech.multiblock.computation.not_enough_computation=Machine needs more comput
 gregtech.multiblock.power_substation.stored=Stored: %s
 gregtech.multiblock.power_substation.capacity=Capacity: %s
 gregtech.multiblock.power_substation.passive_drain=Passive Drain: %s
-gregtech.multiblock.power_substation.average_io=Avg. I/O: %s
-gregtech.multiblock.power_substation.average_io_hover=The average change in energy of the Power Substation's internal energy bank
+gregtech.multiblock.power_substation.average_in=Avg. EU IN: %s
+gregtech.multiblock.power_substation.average_out=Avg. EU OUT: %s
+gregtech.multiblock.power_substation.average_in_hover=The average EU/t input into the Power Substation's internal energy bank
+gregtech.multiblock.power_substation.average_out_hover=The average EU/t output from the Power Substation's internal energy bank, both passive loss and outputs
 gregtech.multiblock.power_substation.time_to_fill=Time to fill: %s
 gregtech.multiblock.power_substation.time_to_drain=Time to drain: %s
 gregtech.multiblock.power_substation.time_seconds=%s Seconds
diff --git a/src/main/resources/assets/gregtech/lang/ru_ru.lang b/src/main/resources/assets/gregtech/lang/ru_ru.lang
index 6150191749e..ffdafa7a6c6 100644
--- a/src/main/resources/assets/gregtech/lang/ru_ru.lang
+++ b/src/main/resources/assets/gregtech/lang/ru_ru.lang
@@ -42,8 +42,8 @@ gregtech.multiblock.steam.low_steam=Недостаточно пара для р
 gregtech.multiblock.steam.steam_stored=Пар: %s / %s mB
 gregtech.machine.steam_hatch.name=Паровой люк
 gregtech.machine.steam.steam_hatch.tooltip=§eПринимает жидкости: §fПар
-gregtech.machine.steam_import_bus.name=Входной предметный люк (Пар)
-gregtech.machine.steam_export_bus.name=Выходной предметный люк (Пар)
+gregtech.machine.steam_import_bus.name=Паровой входной люк
+gregtech.machine.steam_export_bus.name=Паровой выходной люк
 gregtech.machine.steam_bus.tooltip=Не работает с электрическими многоблочными структурами
 gregtech.machine.steam_oven.name=Паровая мультиплавильня
 gregtech.multiblock.steam_oven.description=Мультиплавильня в паровой эпохе. Требует как минимум 6 бронзовых машинных корпусов. Может использовать только паровые люки. Паровой люк должен находится на нижнем слое и он может быть только один.
@@ -52,7 +52,7 @@ gregtech.multiblock.steam_.duration_modifier=Работает в §f1.5x §7ра
 gregtech.top.working_disabled=Работа остановлена
 gregtech.top.energy_consumption=Использует
 gregtech.top.energy_production=Производит
-gregtech.top.transform_up=§cПовышает§r
+gregtech.top.transform_up=Повышает
 gregtech.top.transform_down=§aПонижает§r
 gregtech.top.transform_input=Вход:
 gregtech.top.transform_output=Выход:
@@ -102,7 +102,7 @@ gregtech.multiblock.extreme_combustion_engine.description=Улучшенный 
 gregtech.multiblock.distillation_tower.description=Ректификационная колонна - представляет собой многоблочную структуру, используемую для перегонки различных типов нефти и некоторых их побочных продуктов.
 gregtech.multiblock.electric_blast_furnace.description=Электрическая доменная печь (ЭДП) - это многоблочная структура, используемая для выплавки сплавов, приготовления металлов и переработки руд. Она необходима для получения высокоуровневых сплавов и металлов, таких как алюминий, нержавеющая сталь и титан, сплав наквада.
 gregtech.multiblock.multi_furnace.description=Мульти-плавильный завод - это многоблочная структура, используемая для одновременной выплавки большого количества изделий. Различные уровни катушек обеспечивают увеличение скорости и повышение энергоэффективности. 32 - это базовое значение предметов, выплавляемых за одну операцию, и его можно умножить, используя катушки более высокого уровня.
-gregtech.multiblock.large_boiler.description=Большой котел - представляет собой многоблочную структуру, которая генерируют пар из источника энергии и воды. Обычно либо твердое топливо, либо горючая жидкость высокой плотности действует как источник энергии для большого котла. Уровни отличаются только количеством вырабатываемого пара.
+gregtech.multiblock.large_boiler.description=Большой котел - представляет собой многоблочную структуру, которая генерируют пар из источника энергии и воды. Обычно либо твердое топливо, либо горючая жидкость высокой плотности действует как источник энергии для большого котла. Уровни отличаются только кол-вом вырабатываемого пара.
 gregtech.multiblock.large_turbine.description=Большая турбина - это многоблочная структура, которые вырабатывают энергию из пара, газов и плазмы, вращая ротор турбины. Выход энергии зависит от КПД ротора и текущей скорости турбины.
 gregtech.multiblock.assembly_line.description=Сборочная линия представляет собой большую многоблочную конструкцию, состоящую из 5-16 «кусочков». Теоретически это большая сборочная машина, используемая для создания продвинутых компонентов для крафта.
 gregtech.multiblock.fusion_reactor.luv.description=Термоядерный реактор модель 1 - это большая многоблочная структура использующаяся для термоядерного синтеза двух элементов в один. Может использовать LuV+ энергетические разъемы. За каждый энергетический разъём, внутренний буфер увеличивается на 10млн. EU, максимум энергии для старта 160млн. EU.
@@ -110,17 +110,17 @@ gregtech.multiblock.fusion_reactor.zpm.description=Термоядерный ре
 gregtech.multiblock.fusion_reactor.uv.description=Термоядерный реактор модель 3 - это большая многоблочная структура использующаяся для термоядерного синтеза двух элементов в один. Может использовать UV+ энергетические разъемы. За каждый энергетический разъём, внутренний буфер увеличивается на 40млн. EU, максимум энергии для старта 640млн. EU.
 gregtech.multiblock.fusion_reactor.heat=Нагрев: %d
 gregtech.multiblock.large_chemical_reactor.description=Многоблочная версия химического реактора выполняющий операции в больших объёмах с удвоенной эффективностью. Ускорение умножает скорость и энергию на 4. Для мультиблочной структуры требуется ровно 1 блок купроникелевой катушки, который должен быть размещен рядом с корпусом ПТФЭ трубы, расположенным в центре.
-gregtech.multiblock.primitive_water_pump.description=Примитивная водяная помпа — это многоблочная структура до Паровой Эпохи, который собирает воду раз в секунду, в зависимости от биома, в котором он находится. Он может использовать насос, выходной люк ULV или LV, увеличивая количество воды от уровня. Выполняется по формуле: Коэффициент биома * Множитель люка.
-gregtech.multiblock.primitive_water_pump.extra1=Коэффициент биома:/n  Океан, Река: 1000 л/с/н  Болото: 800 л/с/н  Джунгли: 350 л/с/н  Снежный: 300 л/с/н  Равнины, Лес: 250 л/с/н  Тайга : 175 л/с/н  Пляж: 170 л/с/н  Другое: 100 л/с
+gregtech.multiblock.primitive_water_pump.description=Примитивная водяная помпа — это многоблочная структура до Паровой Эпохи, который собирает воду раз в секунду, в зависимости от биома, в котором он находится. Он может использовать насос, выходной люк ULV или LV, увеличивая кол-во воды от уровня. Выполняется по формуле: Коэф. биома * Множитель люка.
+gregtech.multiblock.primitive_water_pump.extra1=Коэф. биома:/n  Океан, Река: 1000 Л/с/n  Болото: 800 Л/с/n  Джунгли: 350 Л/с/n  Снежный: 300 Л/с/н  Равнины, Лес: 250 Л/с/n  Тайга : 175 Л/с/n  Пляж: 170 Л/с/n  Другое: 100 Л/с
 gregtech.multiblock.primitive_water_pump.extra2=Множители люка:/n  Люк насоса: 1x/n  Выходной люк ULV: 2x/n  LV Выходной люк: 4xn/nВо время дождя в биомах работы насоса добыча воды будет увеличена на 50%%.
 gregtech.multiblock.processing_array.description=Массив машин объединяет до 16 одноблочных машин в одном многоблоке.
 gregtech.multiblock.advanced_processing_array.description=Массив машин объединяет до 64 одноблочных машин в одном многоблоке.
 item.invalid.name=Недопустимый предмет
 fluid.empty=Пусто
-gregtech.tooltip.hold_shift=Зажмите SHIFT для дополнительной информации
-gregtech.tooltip.hold_ctrl=Нажмите CTRL, чтобы увидеть больше информации
-gregtech.tooltip.fluid_pipe_hold_shift=Зажмите SHIFT что-бы посмотреть Информацию о Хранимой Жидкости
-gregtech.tooltip.tool_fluid_hold_shift=Зажмите SHIFT что-бы посмотреть Информацию о Инструменте или Хранимой Жидкости
+gregtech.tooltip.hold_shift=Зажмите SHIFT для Информации
+gregtech.tooltip.hold_ctrl=Зажмите CTRL для Информации
+gregtech.tooltip.fluid_pipe_hold_shift=Зажмите SHIFT для Информации о Жидкости
+gregtech.tooltip.tool_fluid_hold_shift=Зажмите SHIFT для Информации о Инструменте или Жидкости
 metaitem.generic.fluid_container.tooltip=%d/%dЛ %s
 metaitem.generic.electric_item.tooltip=%d/%d EU - Уровень §e%d
 metaitem.electric.discharge_mode.enabled=§eРежим разрядки Включен
@@ -130,19 +130,19 @@ metaitem.dust.tooltip.purify=Бросьте в котел, чтобы получ
 metaitem.crushed.tooltip.purify=Бросьте в котел, чтобы получить очищенную руду
 metaitem.int_circuit.configuration=§aКонфигурация: §f%d§7
 metaitem.credit.copper.name=Медный кредит
-metaitem.credit.copper.tooltip=0,125 кредита
+metaitem.credit.copper.tooltip=0,125 Кредита
 metaitem.credit.cupronickel.name=Купроникелевый кредит
-metaitem.credit.cupronickel.tooltip=1 кредит
+metaitem.credit.cupronickel.tooltip=1 Кредит
 metaitem.credit.silver.name=Серебряный кредит
-metaitem.credit.silver.tooltip=8 кредитов
+metaitem.credit.silver.tooltip=8 Кредитов
 metaitem.credit.gold.name=Золотой кредит
-metaitem.credit.gold.tooltip=64 кредита
+metaitem.credit.gold.tooltip=64 Кредита
 metaitem.credit.platinum.name=Платиновый кредит
-metaitem.credit.platinum.tooltip=512 кредитов
+metaitem.credit.platinum.tooltip=512 Кредитов
 metaitem.credit.osmium.name=Осмиевый кредит
-metaitem.credit.osmium.tooltip=4096 кредитов
+metaitem.credit.osmium.tooltip=4096 Кредитов
 metaitem.credit.naquadah.name=Наквадовый кредит
-metaitem.credit.naquadah.tooltip=32768 кредитов
+metaitem.credit.naquadah.tooltip=32768 Кредитов
 metaitem.credit.neutronium.name=Нейтрониевый кредит
 metaitem.credit.neutronium.tooltip=262144 Кредитов
 metaitem.coin.gold.ancient.name=Древняя золотая монета
@@ -164,7 +164,7 @@ metaitem.nano_saber.tooltip=Ryujin no ken wo kurae!
 metaitem.shape.mold.plate.name=Форма (Пластина)
 metaitem.shape.mold.plate.tooltip=Форма для изготовления пластин
 metaitem.shape.mold.casing.name=Форма (Корпус)
-metaitem.shape.mold.casing.tooltip=Форма для изготовления оболочек
+metaitem.shape.mold.casing.tooltip=Форма для изготовления корпусов
 metaitem.shape.mold.gear.name=Форма (Шестерня)
 metaitem.shape.mold.gear.tooltip=Форма для изготовления шестерней
 metaitem.shape.mold.credit.name=Форма (Чеканка)
@@ -177,7 +177,7 @@ metaitem.shape.mold.ball.name=Форма (Шар)
 metaitem.shape.mold.ball.tooltip=Форма для изготовления шариков
 metaitem.shape.mold.block.name=Форма (Блок)
 metaitem.shape.mold.block.tooltip=Форма для изготовления блоков
-metaitem.shape.mold.nugget.name=Форма (Самородки)
+metaitem.shape.mold.nugget.name=Форма (Самородок)
 metaitem.shape.mold.nugget.tooltip=Форма для изготовления самородков
 metaitem.shape.mold.cylinder.name=Форма (Цилиндр)
 metaitem.shape.mold.cylinder.tooltip=Форма для формирования цилиндров
@@ -186,7 +186,7 @@ metaitem.shape.mold.anvil.tooltip=Форма для формирования н
 metaitem.shape.mold.name.name=Форма (Имя)
 metaitem.shape.mold.name.tooltip=Форма для наименования предметов (переименуйте форму с помощью наковальни)
 metaitem.shape.mold.gear.small.name=Форма (Малая шестерня)
-metaitem.shape.mold.gear.small.tooltip=Форма для изготовления маленьких зубчатых колес
+metaitem.shape.mold.gear.small.tooltip=Форма для изготовления маленьких шестерней
 metaitem.shape.mold.rotor.name=Форма (Ротор)
 metaitem.shape.mold.rotor.tooltip=Форма для изготовления роторов
 metaitem.shape.extruder.plate.name=Форма экструдера (Пластина)
@@ -197,16 +197,16 @@ metaitem.shape.extruder.bolt.name=Форма экструдера (Болт)
 metaitem.shape.extruder.bolt.tooltip=Форма экструдера для изготовления болтов
 metaitem.shape.extruder.ring.name=Форма экструдера (Кольцо)
 metaitem.shape.extruder.ring.tooltip=Форма экструдера для изготовления колец
-metaitem.shape.extruder.cell.name=Форма экструдера (Ячейка)
-metaitem.shape.extruder.cell.tooltip=Форма экструдера для изготовления клеток
+metaitem.shape.extruder.cell.name=Форма экструдера (Капсула)
+metaitem.shape.extruder.cell.tooltip=Форма экструдера для изготовления капсул
 metaitem.shape.extruder.ingot.name=Форма экструдера (Слиток)
-metaitem.shape.extruder.ingot.tooltip=Форма экструдера, подожди, мы не можем просто использовать печь?
+metaitem.shape.extruder.ingot.tooltip=Форма экструдера, подожди, может просто использовать печь?
 metaitem.shape.extruder.wire.name=Форма экструдера (Проволока)
 metaitem.shape.extruder.wire.tooltip=Форма экструдера для изготовления проводов
 metaitem.shape.extruder.casing.name=Форма экструдера (Корпус)
-metaitem.shape.extruder.casing.tooltip=Форма экструдера для изготовления оболочек
-metaitem.shape.extruder.pipe.tiny.name=Форма экструдера (Трубка)
-metaitem.shape.extruder.pipe.tiny.tooltip=Форма экструдера для изготовления трубок
+metaitem.shape.extruder.casing.tooltip=Форма экструдера для изготовления корпусов
+metaitem.shape.extruder.pipe.tiny.name=Форма экструдера (Очень маленькая труба)
+metaitem.shape.extruder.pipe.tiny.tooltip=Форма экструдера для изготовления очень маленьких труб
 metaitem.shape.extruder.pipe.small.name=Форма экструдера (Малая труба)
 metaitem.shape.extruder.pipe.small.tooltip=Форма экструдера для изготовления небольших труб
 metaitem.shape.extruder.pipe.normal.name=Форма экструдера (Средняя труба)
@@ -234,15 +234,15 @@ metaitem.shape.extruder.file.tooltip=Форма экструдера для из
 metaitem.shape.extruder.saw.name=Форма экструдера (Пильный диск)
 metaitem.shape.extruder.saw.tooltip=Форма экструдера для изготовления пил
 metaitem.shape.extruder.gear.name=Форма экструдера (Шестерня)
-metaitem.shape.extruder.gear.tooltip=Форма экструдера для изготовления зубчатых колес
+metaitem.shape.extruder.gear.tooltip=Форма экструдера для изготовления шестерней
 metaitem.shape.extruder.bottle.name=Форма экструдера (Бутылка)
 metaitem.shape.extruder.bottle.tooltip=Форма экструдера для изготовления бутылок
 metaitem.shape.extruder.gear_small.name=Форма экструдера (маленькая шестеренка)
-metaitem.shape.extruder.gear_small.tooltip=Форма экструдера для производства маленьких шестеренок
+metaitem.shape.extruder.gear_small.tooltip=Форма экструдера для производства маленьких шестереней
 metaitem.shape.extruder.foil.name=Форма экструдера (Фольга)
 metaitem.shape.extruder.foil.tooltip=Форма экструдера для производства фольги из неметаллов
 metaitem.shape.extruder.rod_long.name=Форма экструдера (Длинный прут)
-metaitem.shape.extruder.rod_long.tooltip=Форма экструдера для производства длинных прутов
+metaitem.shape.extruder.rod_long.tooltip=Форма экструдера для производства прутьев
 metaitem.shape.extruder.rotor.name=Форма экструдера (Ротор)
 metaitem.shape.extruder.rotor.tooltip=Форма экструдера для производства роторов
 metaitem.spray.empty.name=Баллончик (Пустой)
@@ -386,7 +386,7 @@ metaitem.fluid.regulator.iv.name=Регулятор жидкости (§1IV§r)
 metaitem.fluid.regulator.luv.name=Регулятор жидкости (§dLuV§r)
 metaitem.fluid.regulator.zpm.name=Регулятор жидкости (§fZPM§r)
 metaitem.fluid.regulator.uv.name=Регулятор жидкости (§3UV§r)
-metaitem.fluid.regulator.tooltip=Ограничивает передачу §fЖидкостей§7 заданным количеством как §fУлучшение машины§7.
+metaitem.fluid.regulator.tooltip=Ограничивает передачу §fЖидкостей§7 заданным кол-вом как §fУлучшение машины§7.
 metaitem.conveyor.module.lv.name=Конвейерный модуль (§7LV§r)
 metaitem.conveyor.module.mv.name=Конвейерный модуль (§bMV§r)
 metaitem.conveyor.module.hv.name=Конвейерный модуль (§6HV§r)
@@ -427,7 +427,7 @@ metaitem.robot.arm.uev.name=Роботизированный манипулят
 metaitem.robot.arm.uiv.name=Роботизированный манипулятор (§2UIV§r)
 metaitem.robot.arm.uxv.name=Роботизированный манипулятор (§eUXV§r)
 metaitem.robot.arm.opv.name=Роботизированный манипулятор (§9OpV§r)
-metaitem.robot.arm.tooltip=Ограничивает передачу §fПредметов§7 заданным количеством как §fУлучшение механизма§7.
+metaitem.robot.arm.tooltip=Ограничивает передачу §fПредметов§7 заданным кол-вом как §fУлучшение механизма§7.
 metaitem.field.generator.lv.name=Генератор поля (§7LV§r)
 metaitem.field.generator.mv.name=Генератор поля (§bMV§r)
 metaitem.field.generator.hv.name=Генератор поля (§6HV§r)
@@ -511,7 +511,7 @@ metaitem.wafer.naquadah.name=Плаcтина легированная наква
 metaitem.wafer.naquadah.tooltip=Необработанная схема
 metaitem.wafer.neutronium.name=Пластина легированная нейтронием
 metaitem.wafer.neutronium.tooltip=Необработанная схема
-metaitem.board.coated.name=Прорезиненая подложка
+metaitem.board.coated.name=Прорезиненная подложка
 metaitem.board.coated.tooltip=Подложка с покрытием
 metaitem.board.phenolic.name=Профеноленая подложка
 metaitem.board.phenolic.tooltip=Хорошая подложка
@@ -521,7 +521,7 @@ metaitem.board.epoxy.name=Эпоксидная подложка
 metaitem.board.epoxy.tooltip=Продвинутая подложка
 metaitem.board.fiber_reinforced.name=Укрепленная эпоксидная подложка
 metaitem.board.fiber_reinforced.tooltip=Улучшенная подложка
-metaitem.board.multilayer.fiber_reinforced.name=Многослойная текстолитовая печатная плата
+metaitem.board.multilayer.fiber_reinforced.name=Многослойная текстолитовая подложка
 metaitem.board.multilayer.fiber_reinforced.tooltip=Превосходная подложка
 metaitem.board.wetware.name=Питательная подложка
 metaitem.board.wetware.tooltip=Подложка, которая хранит жизнь
@@ -572,9 +572,9 @@ metaitem.component.advanced_smd.resistor.name=Улучшенный SMD рези
 metaitem.component.advanced_smd.resistor.tooltip=Улучшенный электронный компонент
 metaitem.component.advanced_smd.inductor.name=Улучшенный SMD индуктор
 metaitem.component.advanced_smd.inductor.tooltip=Улучшенный электронный компонент
-metaitem.wafer.highly_advanced_system_on_chip.name=Пластина HASoC
-metaitem.wafer.highly_advanced_system_on_chip.tooltip=Необработанная высоковольтажная микросхема
-metaitem.wafer.advanced_system_on_chip.name=Пластина ASoC
+metaitem.wafer.highly_advanced_system_on_chip.name=Пластина ОУСнК
+metaitem.wafer.highly_advanced_system_on_chip.tooltip=Необработанная очень продвинутая микросхема
+metaitem.wafer.advanced_system_on_chip.name=Пластина УСнК
 metaitem.wafer.advanced_system_on_chip.tooltip=Необработанная продвинутая микросхема
 metaitem.wafer.integrated_logic_circuit.name=Пластина IC
 metaitem.wafer.integrated_logic_circuit.tooltip=Необработанная интегральная микросхема
@@ -592,17 +592,17 @@ metaitem.wafer.low_power_integrated_circuit.name=Пластина LPIC
 metaitem.wafer.low_power_integrated_circuit.tooltip=Необработанная низковольтажная микросхема
 metaitem.wafer.power_integrated_circuit.name=Пластина PIC
 metaitem.wafer.power_integrated_circuit.tooltip=Необработанная силовая микросхема
-metaitem.wafer.nano_central_processing_unit.name=Пластина Нано-процессора
+metaitem.wafer.nano_central_processing_unit.name=Пластина нано-процессора
 metaitem.wafer.nano_central_processing_unit.tooltip=Необработанная нано-микросхема
 metaitem.wafer.nor_memory_chip.name=Пластина NOR-памяти
 metaitem.wafer.nor_memory_chip.tooltip=Необработанная логическая схема
-metaitem.wafer.qbit_central_processing_unit.name=Пластина Qubit процессора
-metaitem.wafer.qbit_central_processing_unit.tooltip=Необработанная Qubit микросхема
+metaitem.wafer.qbit_central_processing_unit.name=Пластина кубитного процессора
+metaitem.wafer.qbit_central_processing_unit.tooltip=Необработанная кубитная микросхема
 metaitem.wafer.random_access_memory.name=Пластина RAM
 metaitem.wafer.random_access_memory.tooltip=Необработанная память
-metaitem.wafer.system_on_chip.name=Пластина SoC
+metaitem.wafer.system_on_chip.name=Пластина СнК
 metaitem.wafer.system_on_chip.tooltip=Необработанная обычная микросхема
-metaitem.wafer.simple_system_on_chip.name=Простая пластина SoC
+metaitem.wafer.simple_system_on_chip.name=Простая пластина СнК
 metaitem.wafer.simple_system_on_chip.tooltip=Необработанная простая микросхема
 metaitem.engraved.crystal_chip.name=Гравированный кристальный чип
 metaitem.engraved.crystal_chip.tooltip=Требуется для микросхем
@@ -613,12 +613,12 @@ metaitem.crystal.raw_chip.tooltip=Компонент необработанно
 metaitem.engraved.lapotron_chip.name=Гравированный лапотронный кристальный чип
 metaitem.crystal.central_processing_unit.name=Кристалл CPU
 metaitem.crystal.central_processing_unit.tooltip=Кристалл блока обработки
-metaitem.crystal.system_on_chip.name=Кристальный SoC
-metaitem.crystal.system_on_chip.tooltip=Кристаллическая система на чипе
-metaitem.plate.advanced_system_on_chip.name=ASoC
-metaitem.plate.advanced_system_on_chip.tooltip=Улучшенная система в чипе
-metaitem.plate.highly_advanced_system_on_chip.name=HASoC
-metaitem.plate.highly_advanced_system_on_chip.tooltip=Очень улучшенная система в чипе
+metaitem.crystal.system_on_chip.name=Кристальный СнК
+metaitem.crystal.system_on_chip.tooltip=Кристаллическая система на кристалле
+metaitem.plate.advanced_system_on_chip.name=УСнК
+metaitem.plate.advanced_system_on_chip.tooltip=Улучшенная система на кристалле
+metaitem.plate.highly_advanced_system_on_chip.name=ОУСнК
+metaitem.plate.highly_advanced_system_on_chip.tooltip=Очень улучшенная система на кристалле
 metaitem.plate.integrated_logic_circuit.name=Интегральная схема
 metaitem.plate.integrated_logic_circuit.tooltip=Интегральная логическая схема
 metaitem.plate.central_processing_unit.name=Центральный процессор
@@ -643,10 +643,10 @@ metaitem.plate.qbit_central_processing_unit.name=Кубитный процесс
 metaitem.plate.qbit_central_processing_unit.tooltip=Кубитный центральный процессор
 metaitem.plate.random_access_memory.name=ОЗУ
 metaitem.plate.random_access_memory.tooltip=Оперативная память
-metaitem.plate.system_on_chip.name=SoC
-metaitem.plate.system_on_chip.tooltip=Система в чипе
-metaitem.plate.simple_system_on_chip.name=Обычная SoC
-metaitem.plate.simple_system_on_chip.tooltip=Простая система на чипе
+metaitem.plate.system_on_chip.name=СнК
+metaitem.plate.system_on_chip.tooltip=Система на кристалле
+metaitem.plate.simple_system_on_chip.name=Обычная СнК
+metaitem.plate.simple_system_on_chip.tooltip=Простая система на кристалле
 
 
 # T1: Electronic
@@ -667,7 +667,7 @@ metaitem.circuit.advanced_integrated.tooltip=Меньше и мощнее/n§6HV
 metaitem.circuit.nand_chip.name=NAND чип
 metaitem.circuit.nand_chip.tooltip=Превосходная простая микросхема/n§6ULV уровень
 metaitem.circuit.microprocessor.name=Микропроцессор
-metaitem.circuit.microprocessor.tooltip=Улучшенная обычная микросхема/n§eLV уровень
+metaitem.circuit.microprocessor.tooltip=Совершенная обычная микросхема/n§eLV уровень
 
 # T3: Processor
 metaitem.circuit.processor.name=Интегральный микропроцессор
@@ -759,11 +759,11 @@ metaitem.cover.fluid.detector.tooltip=Выдает §fЗаполнение жи
 metaitem.cover.fluid.detector.advanced.name=Улучшенный детектор жидкости (Улучшение)
 metaitem.cover.fluid.detector.advanced.tooltip=Позволяет §fRS-триггеру§7 управлять §fСостоянием Хранилища жидкости§7 как редстоунов, в качестве §fКрышки§7.
 metaitem.cover.item.detector.name=Детектор предметов (Улучшение)
-metaitem.cover.item.detector.tooltip=Выдает §fКоличество предметов§7 Сигналом Красного камня как §fУлучшение механизма§7.
+metaitem.cover.item.detector.tooltip=Выдает §fКол-во предметов§7 Сигналом Красного камня как §fУлучшение механизма§7.
 metaitem.cover.item.detector.advanced.name=Улучшенный детектор предметов (Улучшение)
 metaitem.cover.item.detector.advanced.tooltip=Позволяет §fRS-триггеру§7 управлять §fСостоянием Хранилища предметов§7 как редстоунов, в качестве §fКрышки§7.
 metaitem.cover.energy.detector.name=Детектор энергии (Улучшение)
-metaitem.cover.energy.detector.tooltip=Выдает §fКоличество энергии§7 Сигналом Красного камня как §fУлучшение механизма§7.
+metaitem.cover.energy.detector.tooltip=Выдает §fКол-во энергии§7 Сигналом Красного камня как §fУлучшение механизма§7.
 metaitem.cover.energy.detector.advanced.name=Улучшенный детектор энергии (Улучшение)
 metaitem.cover.energy.detector.advanced.tooltip=Даёт §fRS-триггерный контроль за Уровнем энергии§7 Сигналом Красного камня как §fУлучшение механизма§7.
 metaitem.cover.fluid.voiding.name=Удаление жидкостей (Улучшение)
@@ -777,7 +777,7 @@ metaitem.cover.item.voiding.advanced.tooltip=Удаляет §fПредметы
 metaitem.cover.storage.name=Хранилище (Улучшение)
 metaitem.cover.storage.tooltip=Небольшое хранилище для хранения мелочей
 metaitem.cover.maintenance.detector.name=Детектор неисправностей (Улучшение)
-metaitem.cover.maintenance.detector.tooltip=Выдает §Количество Неисправностей§7 Сигналом Красного камня как §fУлучшение механизма§7.
+metaitem.cover.maintenance.detector.tooltip=Выдает §Кол-во Неисправностей§7 Сигналом Красного камня как §fУлучшение механизма§7.
 metaitem.cover.facade.name=Фасад (%s)
 metaitem.cover.facade.tooltip=Декоративный элемент.
 metaitem.cover.screen.name=Компьютерный монитор (Улучшение)
@@ -827,7 +827,7 @@ metaitem.plant_ball.name=Комок биомассы
 metaitem.tool_parts_box.name=Ящик для инструментов
 metaitem.tool_parts_box.tooltip=Для хранения инструментов/nЩелкните правой кнопкой, чтобы открыть
 metaitem.foam_sprayer.name=Пенный распылитель
-metaitem.foam_sprayer.tooltip=Распыляет строительную пену/nЩелкните правой кнопкой, чтобы запенить соединенные рамки./nИспользуйте металл для создания армированного бетона/nПена может быть цветной
+metaitem.foam_sprayer.tooltip=Распыляет строительную пену/nЩелкните правой кнопкой, чтобы запенить соединенные Каркасы./nИспользуйте металл для создания армированного бетона/nПена может быть цветной
 metaitem.energium_dust.name=Энергетическая пыль
 metaitem.compressed.clay.name=Сжатая глина
 metaitem.compressed.fireclay.name=Сжатая шамотная глина
@@ -937,7 +937,7 @@ item.gt.tool.harvest_level.3=§bАлмаз
 item.gt.tool.harvest_level.4=§dУльтимет
 item.gt.tool.harvest_level.5=§9Дураний
 item.gt.tool.harvest_level.6=§cНейтроний
-item.gt.tool.tooltip.repair_info=Удерживайте SHIFT для информации о Ремонте
+item.gt.tool.tooltip.repair_info=Зажмите SHIFT для Информации о Ремонте
 item.gt.tool.tooltip.repair_material=Чинится: §a%s
 item.gt.tool.aoe.rows=Рядов
 item.gt.tool.aoe.columns=Столбцов
@@ -1051,14 +1051,14 @@ metaitem.nan.certificate.tooltip=Вызов принят!
 metaitem.fertilizer.name=Удобрение
 metaitem.blacklight.name=Черный свет
 metaitem.blacklight.tooltip=Источник длинноволнового §dультрафиолетового§7 излучения
-gui.widget.incrementButton.default_tooltip=Удерживайте Shift, Ctrl или оба, чтобы изменить количество
+gui.widget.incrementButton.default_tooltip=Удерживайте Shift, Ctrl или оба, чтобы изменить кол-во
 gui.widget.recipeProgressWidget.default_tooltip=Показать рецепт
 gregtech.recipe_memory_widget.tooltip.2=§7Нажмите Shift, чтобы заблокировать/разблокировать этот рецепт
 gregtech.recipe_memory_widget.tooltip.1=§7Щелкните левой кнопкой мыши, чтобы автоматически добавить этот рецепт в сетку крафта
 cover.filter.blacklist.disabled=Белый список
 cover.filter.blacklist.enabled=Черный список
 cover.ore_dictionary_filter.title=Фильтр по словарю руд
-cover.ore_dictionary_filter.info=§bПринимает сложные выражения/n§6a & b§r = AND/n§6a | b§r = OR/n§6a ^ b§r = XOR/n§6! abc§r = NOT/n§6( abc )§r для группировки/n§6*§r для подстановочного знака/n§6?§r для любого 1 символа/n§6()§r для пустого места (включая предметы из словаря руд)/n§6$c§r для начала выражения с учетов регистра/n§bПример:/n§6dust*Gold | (plate* & !*Double*)/nПодходит для всей золотой пыли всех размеров или всех пластин, но не для двойных пластин
+cover.ore_dictionary_filter.info=§bПринимает сложные выражения/n§6a & b§r = AND/n§6a | b§r = OR/n§6a ^ b§r = XOR/n§6! abc§r = NOT/n§6( abc )§r для группировки/n§6*§r для подстановочного знака/n§6?§r для любого 1 символа/n§6()§r для пустого места (включая предметы из словаря руд)/n§bПример:/n§6dust*Gold | (plate* & !*Double*)/nПодходит для всей золотой пыли всех размеров или всех пластин, но не для двойных пластин
 cover.ore_dictionary_filter.test_slot.info=Вставьте элемент, чтобы проверить, соответствует ли он выражению фильтра
 cover.ore_dictionary_filter.test_slot.matches=§a* %s
 cover.ore_dictionary_filter.test_slot.matches_not=§c* %s
@@ -1070,7 +1070,7 @@ cover.ore_dictionary_filter.status.warn=§7%s предупреждение(я)
 cover.ore_dictionary_filter.status.no_issues=§aНет проблем
 cover.ore_dictionary_filter.status.explain=Объяснение фильтра руды:
 cover.fluid_filter.title=Жидкостный фильтр
-cover.fluid_filter.config_amount=Колесо прокрутки вверх увеличивает количество, вниз уменьшает./nShift[§6x10§r],Ctrl[§ex100§r],Shift+Ctrl[§ax1000§r]/nПравый клик увеличивает количество, левый уменьшает./nУдерживайте Shift, чтобы удвоить/уполовинить./nЩелкните средней кнопкой, чтобы очистить
+cover.fluid_filter.config_amount=Колесо прокрутки вверх увеличивает количество, вниз уменьшает./nShift[§6x10§r],Ctrl[§ex100§r],Shift+Ctrl[§ax1000§r]/nПравый клик увеличивает кол-во, левый уменьшает./nУдерживайте Shift, чтобы удвоить/уполовинить./nЩелкните средней кнопкой, чтобы очистить
 cover.fluid_filter.mode.filter_fill=Фильтрует при наполнении
 cover.fluid_filter.mode.filter_drain=Фильтрует при сливе
 cover.fluid_filter.mode.filter_both=Фильтрует при сливе и наполнении
@@ -1098,7 +1098,7 @@ cover.smart_item_filter.title=Умный предметный фильтр
 cover.smart_item_filter.filtering_mode.electrolyzer=Электролизер
 cover.smart_item_filter.filtering_mode.centrifuge=Центрифуга
 cover.smart_item_filter.filtering_mode.sifter=Просеиватель
-cover.smart_item_filter.filtering_mode.description=Выберите механизм для которой будут/не будут фильтроваться предметы для роботической руки.
+cover.smart_item_filter.filtering_mode.description=Выберите машину, которую этот Умный фильтр будет использовать для фильтрации./nОн автоматически отберет нужное кол-во предметов для роб. манипулятора.
 cover.conveyor.title=Настройки улучшения конвейера (%s)
 cover.conveyor.transfer_rate=§7предметов/с
 cover.conveyor.mode.export=Режим: Экспорт
@@ -1115,7 +1115,7 @@ cover.conveyor.item_filter.title=Фильтр предметов
 cover.conveyor.ore_dictionary.title=Название по словарю руд
 cover.conveyor.ore_dictionary.title2=(используйте * как подстановочный знак)
 cover.robotic_arm.title=Настройки роб. манипулятора (%s)
-cover.robotic_arm.transfer_mode.transfer_any=Перемещение любое количество
+cover.robotic_arm.transfer_mode.transfer_any=Перемещение любое кол-во
 cover.robotic_arm.transfer_mode.transfer_exact=Перемещать ровно
 cover.robotic_arm.transfer_mode.keep_exact=Поддерживать ровно
 cover.robotic_arm.transfer_mode.description=§eПеремещение любых предметов§r - в этом режиме улучшение будет передавать как можно больше предметов, соответствующих фильтру./n§eПодавать точно§r - в этом режиме улучшение будет поставлять предметы порциями, указанными в слотах фильтра предметов (или скоростью передачи для §bФильтра Словаря руды§r). Если количество предметов меньше размера порции, предметы не будут перемещены ./n§eСохранять точно§r - в этом режиме обложка сохранит указанное количество предметов в целевом инвентаре, предоставляя дополнительное количество предметов, если это необходимо./n§7Подсказка: щелкните Лкм или Пкм на слотах фильтра, чтобы изменить количество предметов,  используйте Shift, чтобы изменять количество быстрее.
@@ -1162,7 +1162,7 @@ cover.advanced_fluid_detector.label=Улучшенный детектор жид
 cover.advanced_fluid_detector.max=Макс. жидкости:
 cover.advanced_fluid_detector.min=Мин. жидкости:
 cover.advanced_item_detector.label=Улучшенный детектор предметов
-cover.advanced_item_detector.invert_tooltip=Переключите, чтобы инвертировать логику красного камня./nПо умолчанию красный камень перестает испускать сигнал, когда количество предметов меньше минимального, и начинает испускать сигнал, при превышении минимального количества предметов до установленного максимума
+cover.advanced_item_detector.invert_tooltip=Переключите, чтобы инвертировать логику красного камня./nПо умолчанию красный камень перестает испускать сигнал, когда кол-во предметов меньше мин., и начинает испускать сигнал, при превышении мин. кол-ва предметов до установленного макс
 cover.advanced_item_detector.max=Макс. предметов:
 cover.advanced_item_detector.min=Мин. предметов:
 cover.storage.title=Хранилище (Улучшение)
@@ -1203,8 +1203,8 @@ item.material.oreprefix.plateDouble=%s (Двойная пластина)
 item.material.oreprefix.plate=%s (Пластина)
 item.material.oreprefix.plank=Доска (%s)
 item.material.oreprefix.foil=%s (Фольга)
-item.material.oreprefix.stick=%s (Прут)
-item.material.oreprefix.stickLong=%s (Стержень)
+item.material.oreprefix.stick=%s (Стержень)
+item.material.oreprefix.stickLong=%s (Прут)
 item.material.oreprefix.round=%s (Шарик)
 item.material.oreprefix.bolt=%s (Болт)
 item.material.oreprefix.screw=%s (Винт)
@@ -1264,7 +1264,7 @@ item.material.oreprefix.polymer.plate=%s (Лист)
 item.material.oreprefix.polymer.foil=%s (Фольга)
 item.material.oreprefix.polymer.nugget=%s (Осколок)
 item.material.oreprefix.polymer.plateDense=%s (Плотный лист)
-item.material.oreprefix.polymer.plateDouble=%s (Плотный лист)
+item.material.oreprefix.polymer.plateDouble=%s (Двойной лист)
 item.material.oreprefix.polymer.dustTiny=%s (Крохотная кучка)
 item.material.oreprefix.polymer.dustSmall=%s (Маленькая кучка)
 item.material.oreprefix.polymer.dust=%s (Пыль)
@@ -1942,7 +1942,7 @@ item.gregtech.material.cassiterite_sand.dustImpure=Грязная кучка к
 item.gregtech.material.cassiterite_sand.dustPure=Очищеная кучка касситеритного песка
 item.gregtech.material.cassiterite_sand.dust=Касситеритовый песок
 item.gregtech.material.dark_ash.dustTiny=Крошечная кучка пепла
-item.gregtech.material.dark_ash.dustSmall=Маленькая кучка темного пепла
+item.gregtech.material.dark_ash.dustSmall=Маленькая кучка пепла
 item.gregtech.material.dark_ash.dust=Пепел
 item.gregtech.material.ice.dustTiny=Крошечная кучка колотого льда
 item.gregtech.material.ice.dustSmall=Маленькая кучка колотого льда
@@ -2006,9 +2006,9 @@ item.gregtech.material.bentonite.dustPure=Очищеный кучка бенто
 item.gregtech.material.bentonite.dustSmall=Маленькая кучка бентонита
 item.gregtech.material.bentonite.dustTiny=Крошечная кучка бентонита
 item.gregtech.material.bentonite.dust=Бентонит
-item.gregtech.material.fullers_earth.dustSmall=Маленькая кучка странной земли
-item.gregtech.material.fullers_earth.dustTiny=Крошечная кучка странной земли
-item.gregtech.material.fullers_earth.dust=Странная земля
+item.gregtech.material.fullers_earth.dustSmall=Маленькая кучка смектической глины
+item.gregtech.material.fullers_earth.dustTiny=Крошечная кучка cмектической глины
+item.gregtech.material.fullers_earth.dust=Смектическая глина
 item.gregtech.material.pitchblende.crushed=Измельчённый уранит
 item.gregtech.material.pitchblende.crushedCentrifuged=Центрифугированный уранит
 item.gregtech.material.pitchblende.crushedPurified=Очищеный уранит
@@ -2187,7 +2187,7 @@ behavior.tricorder.bedrock_fluid.amount=Жидкость в месторожде
 behavior.tricorder.bedrock_fluid.amount_unknown=Жидкость в месторождении: %s%%
 behavior.tricorder.bedrock_fluid.nothing=Жидкость в месторождении: §6Ничего§r
 behavior.tricorder.eut_per_sec=За последняя секунду прошло %s EU/t
-behavior.tricorder.amp_per_sec=За последняя секунду прошло %s A
+behavior.tricorder.amp_per_sec=За последнюю секунду прошло %s A
 behavior.tricorder.workable_consumption=Примерно использует: %s EU/t при %s A
 behavior.tricorder.workable_production=Примерно производит: %s EU/т при %s A
 behavior.tricorder.workable_progress=Прогресс: %s с / %s с
@@ -2239,7 +2239,7 @@ tile.casing.ev=Корпус машины (§5EV§r)
 tile.casing.iv=Корпус машины (§1IV§r)
 
 # Wire coil blocks
-tile.wire_coil.tooltip_extended_info=Зажмите SHIFT для просмотра Бонуса Катушек
+tile.wire_coil.tooltip_extended_info=Зажмите SHIFT для Информации о Бонуса Катушек
 tile.wire_coil.tooltip_heat=§cТеплоемкость: §f%,d K
 tile.wire_coil.tooltip_smelter=§8Мультиплавильня:
 tile.wire_coil.tooltip_parallel_smelter=§5Параллелей: §f%s
@@ -2365,10 +2365,10 @@ tile.machine_casing.overpowered_voltage.name=Корпус машины (OpV)
 tile.machine_casing.maximum_voltage.name=Корпус машины (MAX)
 
 # Steam casing blocks
-tile.steam_casing.bronze_hull.name=Бронзовый корпус
-tile.steam_casing.bronze_bricks_hull.name=Бронзовый кирпичный корпус
-tile.steam_casing.steel_hull.name=Стальной корпус
-tile.steam_casing.steel_bricks_hull.name=Кирпичный корпус из кованого железа
+tile.steam_casing.bronze_hull.name=Бронзовая оболочка
+tile.steam_casing.bronze_bricks_hull.name=Бронзовая кирпичная оболочка
+tile.steam_casing.steel_hull.name=Стальная оболочка
+tile.steam_casing.steel_bricks_hull.name=Кирпичная оболочка из кованого железа
 tile.steam_casing.bronze.tooltip=Для ваших первых паровых машин
 tile.steam_casing.steel.tooltip=Для улучшенных паровых машин
 tile.steam_casing.pump_deck.name=Насосная палуба
@@ -2924,7 +2924,7 @@ gregtech.machine.canner.uhv.name=Идеальный наполнитель
 gregtech.machine.canner.uhv.tooltip=Электроконсерватор
 gregtech.machine.canner.uev.name=Идеальный наполнитель II
 gregtech.machine.canner.uev.tooltip=Электроконсерватор
-gregtech.machine.canner.uiv.name=Идеальный наполнитель II
+gregtech.machine.canner.uiv.name=Идеальный наполнитель III
 gregtech.machine.canner.uiv.tooltip=Электроконсерватор
 gregtech.machine.canner.uxv.name=Идеальный наполнитель IV
 gregtech.machine.canner.uxv.tooltip=Электроконсерватор
@@ -3082,7 +3082,7 @@ gregtech.machine.distillery.zpm.name=Превосходный дистиллят
 gregtech.machine.distillery.uv.name=Безупречный дистиллятор
 gregtech.machine.distillery.uhv.name=Идеальный дистиллятор
 gregtech.machine.distillery.uev.name=Идеальный дистиллятор II
-gregtech.machine.distillery.uiv.name=Идеальный дистиллятор II
+gregtech.machine.distillery.uiv.name=Идеальный дистиллятор III
 gregtech.machine.distillery.uxv.name=Идеальный дистиллятор IV
 gregtech.machine.distillery.lv.tooltip=Извлечение наиболее важных частей жидкостей
 gregtech.machine.distillery.mv.tooltip=Извлечение наиболее важных частей жидкостей
@@ -3472,7 +3472,7 @@ gregtech.machine.polarizer.zpm.name=Превосходный поляризат
 gregtech.machine.polarizer.uv.name=Безупречный поляризатор
 gregtech.machine.polarizer.uhv.name=Идеальный поляризатор
 gregtech.machine.polarizer.uev.name=Идеальный поляризатор II
-gregtech.machine.polarizer.uiv.name=Идеальный поляризатор II
+gregtech.machine.polarizer.uiv.name=Идеальный поляризатор III
 gregtech.machine.polarizer.uxv.name=Идеальный поляризатор IV
 gregtech.machine.polarizer.lv.tooltip=Биполяризация ваших магнитов
 gregtech.machine.polarizer.mv.tooltip=Биполяризация ваших магнитов
@@ -3722,21 +3722,21 @@ gregtech.creative_tooltip.3=§7 чтобы использовать это
 
 # Machine hulls
 gregtech.machine.hull.tooltip=§7Вам просто нужно §5В§dо§4о§cб§eр§aа§bж§3е§7н§1и§5е§7 чтобы использовать это
-gregtech.machine.hull.ulv.name=Корпус машины (§8ULV§r)
-gregtech.machine.hull.lv.name=Корпус машины (§7LV§r)
-gregtech.machine.hull.mv.name=Корпус машины (§bMV§r)
-gregtech.machine.hull.hv.name=Корпус машины (§6HV§r)
-gregtech.machine.hull.ev.name=Корпус машины (§5EV§r)
-gregtech.machine.hull.iv.name=Корпус машины (§1IV§r)
-gregtech.machine.hull.luv.name=Корпус машины (§dLuV§r)
-gregtech.machine.hull.zpm.name=Корпус машины (§fZPM§r)
-gregtech.machine.hull.uv.name=Корпус машины (§3UV§r)
-gregtech.machine.hull.uhv.name=Корпус машины (§4UHV§r)
-gregtech.machine.hull.uev.name=Корпус машины (§aUEV§r)
-gregtech.machine.hull.uiv.name=Корпус машины (§2UIV§r)
-gregtech.machine.hull.uxv.name=Корпус машины (§eUXV§r)
-gregtech.machine.hull.opv.name=Корпус машины (§9OpV§r)
-gregtech.machine.hull.max.name=Корпус машины (§cMAX§r)
+gregtech.machine.hull.ulv.name=Оболочка машины (§8ULV§r)
+gregtech.machine.hull.lv.name=Оболочка машины (§7LV§r)
+gregtech.machine.hull.mv.name=Оболочка машины (§bMV§r)
+gregtech.machine.hull.hv.name=Оболочка машины (§6HV§r)
+gregtech.machine.hull.ev.name=Оболочка машины (§5EV§r)
+gregtech.machine.hull.iv.name=Оболочка машины (§1IV§r)
+gregtech.machine.hull.luv.name=Оболочка машины (§dLuV§r)
+gregtech.machine.hull.zpm.name=Оболочка машины (§fZPM§r)
+gregtech.machine.hull.uv.name=Оболочка машины (§3UV§r)
+gregtech.machine.hull.uhv.name=Оболочка машины (§4UHV§r)
+gregtech.machine.hull.uev.name=Оболочка машины (§aUEV§r)
+gregtech.machine.hull.uiv.name=Оболочка машины (§2UIV§r)
+gregtech.machine.hull.uxv.name=Оболочка машины (§eUXV§r)
+gregtech.machine.hull.opv.name=Оболочка машины (§9OpV§r)
+gregtech.machine.hull.max.name=Оболочка машины (§cMAX§r)
 
 # Battery buffers
 gregtech.machine.battery_buffer.ulv.4.name=Батарейный буфер (4 ячейки §8ULV§r)
@@ -3789,7 +3789,7 @@ gregtech.battery_buffer.average_input=Ввод в среднем: %s EU/t
 
 # Transformers
 gregtech.machine.transformer.description=Преобразует энергию между уровнями напряжения
-gregtech.machine.transformer.higher_amp.description=Преобразует энергию между уровнями напряжения, теперь с большим количеством ампер!
+gregtech.machine.transformer.higher_amp.description=Преобразует энергию между уровнями напряжения, теперь с большим кол-вом ампер!
 gregtech.machine.transformer.tooltip_tool_usage=По умолчанию §fПонижающий режим§7, используйте киянку чтобы инвертировать режим
 gregtech.machine.transformer.tooltip_transform_down=§aПонижающий режим: §f%dA %d EU (%s§f) -> %dA %d EU (%s§f)
 gregtech.machine.transformer.message_transform_down=Понижает напряжение, вход: %d EU %dA, выход: %d EU %dA
@@ -4263,8 +4263,8 @@ gregtech.advancement.extreme_voltage.50_nano_processor.name=Нано-проце
 gregtech.advancement.extreme_voltage.50_nano_processor.desc=Получите нано-процессор.
 gregtech.advancement.extreme_voltage.51_large_combustion_engine.name=Большой дизельный генератор
 gregtech.advancement.extreme_voltage.51_large_combustion_engine.desc=Соберите большой дизельный генератор, снабдите его смазкой и ускорьте кислородом.
-gregtech.advancement.extreme_voltage.52_soc_wafer.name=Пластина SoC
-gregtech.advancement.extreme_voltage.52_soc_wafer.desc=Сделайте Пластину SoC для более дешёвого производства микропроцессоров и интегральных микросхем.
+gregtech.advancement.extreme_voltage.52_soc_wafer.name=Пластина СНК
+gregtech.advancement.extreme_voltage.52_soc_wafer.desc=Сделайте Пластину СНК для более дешёвого производства микропроцессоров и интегральных микросхем.
 gregtech.advancement.root_iv.name=Безумный вольтаж
 gregtech.advancement.root_iv.desc=Охладите горячую вольфрамовую сталь.
 gregtech.advancement.insane_voltage.53_plutonium_239.name=Плутоний-239
@@ -4295,8 +4295,8 @@ gregtech.advancement.ludicrous_voltage.65_naquadah.name=Материал зве
 gregtech.advancement.ludicrous_voltage.65_naquadah.desc=Охладите горячий слиток наквады.
 gregtech.advancement.ludicrous_voltage.66_naquadah_coil.name=Улучшите ваши катушки до уровня VI
 gregtech.advancement.ludicrous_voltage.66_naquadah_coil.desc=Создайте катушку из наквады.
-gregtech.advancement.ludicrous_voltage.67_asoc_wafer.name=Пластина ASoC
-gregtech.advancement.ludicrous_voltage.67_asoc_wafer.desc=Сделайте Пластину SoC для более дешёвого производства нано-процессоров и квантовых процессоров.
+gregtech.advancement.ludicrous_voltage.67_asoc_wafer.name=Пластина УСНК
+gregtech.advancement.ludicrous_voltage.67_asoc_wafer.desc=Сделайте Пластину СНК для более дешёвого производства нано-процессоров и квантовых процессоров.
 gregtech.advancement.ludicrous_voltage.68_large_plasma_turbine.name=Большая плазменная турбина
 gregtech.advancement.ludicrous_voltage.68_large_plasma_turbine.desc=Создайте плазменную турбину, использующую плазму как топливо.
 gregtech.advancement.root_zpm.name=Модуль нулевой точки
@@ -4325,8 +4325,8 @@ gregtech.advancement.ultimate_voltage.76_neutronium.name=Как можно пл
 gregtech.advancement.ultimate_voltage.76_neutronium.desc=Получите нейтроний.
 gregtech.advancement.ultimate_voltage.77_ultimate_battery.name=И что теперь?
 gregtech.advancement.ultimate_voltage.77_ultimate_battery.desc=Создайте безупречную батарею.
-gregtech.advancement.ultimate_voltage.78_hasoc_wafer.name=Пластина HASoC
-gregtech.advancement.ultimate_voltage.78_hasoc_wafer.desc=Сделайте пластину HASoC для более дешёвого производства органических микросхем.
+gregtech.advancement.ultimate_voltage.78_hasoc_wafer.name=Пластина ОУСнК
+gregtech.advancement.ultimate_voltage.78_hasoc_wafer.desc=Сделайте пластину СУСНК для более дешёвого производства органических микросхем.
 gregtech.advancement.ultimate_voltage.79_tritanium_coil.name=Финальная катушка
 gregtech.advancement.ultimate_voltage.79_tritanium_coil.desc=Изготовьте нагревательную катушку из Тритания.
 
@@ -4431,14 +4431,14 @@ gregtech.machine.cleanroom.tooltip.1=Установите машины внут
 gregtech.machine.cleanroom.tooltip.2=Использует §f30 EU/t§7 когда загрязнена, §f4 EU/t§7 когда очищена.
 gregtech.machine.cleanroom.tooltip.3=Разгон увеличивает очистку за один цикл.
 gregtech.machine.cleanroom.tooltip.4=§bРазмер: от §f5x5x5 до 15x15x15
-gregtech.machine.cleanroom.tooltip.hold_ctrl=Зажмите CTRL для просмотра дополнительной информации о структуре
+gregtech.machine.cleanroom.tooltip.hold_ctrl=Зажмите CTRL для Дополнительной информации о структуре
 gregtech.machine.cleanroom.tooltip.5=Требует установку §fКорпуса фильтра §7в любом месте потолка кроме его граней.
 gregtech.machine.cleanroom.tooltip.6=Позволяет установить до §f4 дверей§7! Требует повторную очистку при открытой двери.
 gregtech.machine.cleanroom.tooltip.7=Генераторы, глушители, буры и примитивные машины слишком грязные для чистой комнаты!
-gregtech.machine.cleanroom.tooltip.8=Подавайте питание через §fКорпуса §7или §fДиоды §7в стенах.
+gregtech.machine.cleanroom.tooltip.8=Подавайте питание через §fОболочку §7или §fДиоды §7в стенах.
 gregtech.machine.cleanroom.tooltip.9=Отправляйте предметы и жидкости с помощью §fСквозных люков§7в стенах.
-gregtech.machine.cleanroom.tooltip.ae2.channels=Отправляйте до §f8 AE2 каналов §7через §fКорпуса§7 в стенах.
-gregtech.machine.cleanroom.tooltip.ae2.no_channels=Отправляйте §aAE2 сеть§7 через §fКорпуса§7 в стенах.
+gregtech.machine.cleanroom.tooltip.ae2.channels=Отправляйте до §f8 AE2 каналов §7через §fОболочку§7 в стенах.
+gregtech.machine.cleanroom.tooltip.ae2.no_channels=Отправляйте §aAE2 сеть§7 через §fОболочку§7 в стенах.
 gregtech.multiblock.cleanroom.dirty_state=Статус: §4ЗАГРЯЗНЕНА
 gregtech.multiblock.cleanroom.clean_state=Статус: §aЧИСТАЯ
 gregtech.machine.charcoal_pile.name=Воспламенитель угольной ямы
@@ -4463,10 +4463,10 @@ gregtech.machine.power_substation.tooltip5=Ограниченно §f%,d EU/t§7
 gregtech.multiblock.power_substation.description=Силовая Подстанция представляет собой многоблочную структуру, используемую для хранения огромного количества Энергии. Способен вместить до 18 слоев батарей. Также можно использовать пустые Накопители для заполнения пространства, так как слои должны быть полностью заполнены.
 gregtech.machine.active_transformer.name=Активный Трансформатор
 gregtech.machine.active_transformer.tooltip1=Трансформеры: Замаскированные лазеры
-gregtech.machine.active_transformer.tooltip2=Может комбинировать любое количество Энергетических §fВходных§7 разъемов в любое количество Энергетических §fВыходных§7 разъемов.
+gregtech.machine.active_transformer.tooltip2=Может комбинировать любое кол-во Энергетических §fВходных§7 разъемов в любое кол-во Энергетических §fВыходных§7 разъемов.
 gregtech.machine.active_transformer.tooltip3=Может передавать энергию на невероятное расстояние с помощью
 gregtech.machine.active_transformer.tooltip3.5=Лазеров§7.
-gregtech.multiblock.active_transformer.description=Активный Трансформатор представляет собой многоблочную структуру, которая может принимать любое количество или уровень энергетических входных разъемов преобразовывать их в любое количество или уровень энергетических выходных разъемов. Они могут быть соединены с Люком для Лазерного Источника и Люком для Лазерного Приемника, что позволяет передавать энергию на большие расстояния без каких-либо потерь. Лазеры должны располагаться по прямой, иначе они не будут посылать энергию.
+gregtech.multiblock.active_transformer.description=Активный Трансформатор представляет собой многоблочную структуру, которая может принимать любое кол-во или уровень энергетических входных разъемов преобразовывать их в любое кол-во или уровень энергетических выходных разъемов. Они могут быть соединены с Люком для Лазерного Источника и Люком для Лазерного Приемника, что позволяет передавать энергию на большие расстояния без каких-либо потерь. Лазеры должны располагаться по прямой, иначе они не будут посылать энергию.
 gregtech.machine.research_station.name=Станция исследований
 gregtech.machine.research_station.tooltip.1=Больше чем Многоблочный Сканнер
 gregtech.machine.research_station.tooltip.2=Используется для сканирования §fСфер данных§7 или §fМодулей данных§7.
@@ -4475,13 +4475,13 @@ gregtech.multiblock.research_station.description=Исследовательск
 gregtech.machine.network_switch.name=Коммутатор
 gregtech.machine.network_switch.tooltip.1=Ethernet-концентратор
 gregtech.machine.network_switch.tooltip.2=Используется для маршрутизации и распределения §fВычислений§7.
-gregtech.machine.network_switch.tooltip.3=Можно объединить любое количество §fПриемников§7 Вычислений в любое количество §fПередатчиков§7 Вычислений.
-gregtech.multiblock.network_switch.description=Коммутатор представляет собой многоблочную структуру, используемую для распределения вычислительных ресурсов из многих источников во многие пункты назначения. Он может принимать любое количество Люков Приема или Передачи вычислительных данных. Это необходимо для Исследовательских Данных, которые требуют гораздо больших Вычислений, поскольку исследовательская станция может принимать только один люк для приема данных вычислений. HPCA должен иметь Компонент Моста, чтобы сетевой коммутатор мог получить доступ к своим вычислениям.
+gregtech.machine.network_switch.tooltip.3=Можно объединить любое кол-во §fПриемников§7 Вычислений в любое кол-во §fПередатчиков§7 Вычислений.
+gregtech.multiblock.network_switch.description=Коммутатор представляет собой многоблочную структуру, используемую для распределения вычислительных ресурсов из многих источников во многие пункты назначения. Он может принимать любое кол-во Люков Приема или Передачи вычислительных данных. Это необходимо для Исследовательских Данных, которые требуют гораздо больших Вычислений, поскольку исследовательская станция может принимать только один люк для приема данных вычислений. HPCA должен иметь Компонент Моста, чтобы сетевой коммутатор мог получить доступ к своим вычислениям.
 gregtech.machine.high_performance_computing_array.name=Высокопроизводительный вычислительный массив
 gregtech.machine.high_performance_computing_array.tooltip.1=Просто самый обычный Суперкомпьютер
 gregtech.machine.high_performance_computing_array.tooltip.2=Используется для генерации §fВычислений§7 (и тепла).
 gregtech.machine.high_performance_computing_array.tooltip.3=Требуются компоненты HPCA для создания §fCWU/t§7 (Вычислительные Рабочие Единицы).
-gregtech.multiblock.high_performance_computing_array.description=Высокопроизводительный Вычислительный Массив (HPCA) представляет собой многоблочную структуру, используемую для создания Вычислительные Рабочие Единицы (CWU/t) для более Сложных Данных Исследования Cборочной Линии. Структура имеет гибкую область 3x3, которая может быть заполнена компонентами HPCA любым способом. Различные компоненты могут обеспечивать разное количество вычислений, охлаждения, а также затрат на энергию, стоимость охлаждающей жидкости и производство тепла. При использовании с компонентом моста HPCA может подключаться к сетевым коммутаторам для объединения и маршрутизации вычислений из нескольких источников в одно или несколько мест назначения.
+gregtech.multiblock.high_performance_computing_array.description=Высокопроизводительный Вычислительный Массив (HPCA) представляет собой многоблочную структуру, используемую для создания Вычислительные Рабочие Единицы (CWU/t) для более Сложных Данных Исследования Cборочной Линии. Структура имеет гибкую область 3x3, которая может быть заполнена компонентами HPCA любым способом. Различные компоненты могут обеспечивать разное ко-во вычислений, охлаждения, а также затрат на энергию, стоимость охлаждающей жидкости и производство тепла. При использовании с компонентом моста HPCA может подключаться к сетевым коммутаторам для объединения и маршрутизации вычислений из нескольких источников в одно или несколько мест назначения.
 gregtech.machine.central_monitor.name=Центральный монитор
 gregtech.multiblock.central_monitor.low_power=Недостаточно энергии
 gregtech.multiblock.central_monitor.height=Высота экрана:
@@ -4573,7 +4573,6 @@ gregtech.machine.item_bus.export.uv.name=Предметный выходной 
 gregtech.machine.item_bus.export.uhv.name=Предметный выходной люк (§4UHV§r)
 gregtech.bus.collapse_true=Люк будет совмещать Предметы
 gregtech.bus.collapse_false=Люк не будет совмещать Предметы
-gregtech.bus.collapse.error=Люк должен быть сперва добавлен в многоблочную структуру
 gregtech.machine.fluid_hatch.import.tooltip=Для подачи жидкости в многоблочную структуру
 gregtech.machine.fluid_hatch.import.ulv.name=Жидкостный входной люк (§8ULV§r)
 gregtech.machine.fluid_hatch.import.lv.name=Жидкостный входной люк (§7LV§r)
@@ -4694,7 +4693,7 @@ gregtech.maintenance.configurable_time=Время: %fx
 gregtech.maintenance.configurable_time.unchanged_description=Проблемы с обслуживанием будут возникать с обычной частотой. Измените конфигурацию для обновления.
 gregtech.maintenance.configurable_time.changed_description=Проблемы с обслуживанием будут возникать в %f раз чаще.
 gregtech.maintenance.configurable.tooltip_basic=Ускоряет время работы машины за счет более частых проблем с обслуживанием
-gregtech.maintenance.configurable.tooltip_more_info=Зажмите SHIFT для особого взаимодействия
+gregtech.maintenance.configurable.tooltip_more_info=Зажмите SHIFT для Особого взаимодействия
 gregtech.maintenance.configurable.tooltip_pss_header=§8Силовая Подстанция:
 gregtech.maintenance.configurable.tooltip_pss_info=§fУменьшает пассивную утечку энергии
 gregtech.machine.muffler_hatch.tooltip1=Восстанавливает отходы от машин
@@ -4778,7 +4777,7 @@ gregtech.universal.tooltip.fluid_stored=§dОбъем жидкости: §f%s, %
 gregtech.universal.tooltip.fluid_transfer_rate=§bСкорость передачи: §f%,d Л/т
 gregtech.universal.tooltip.parallel=§dПаралеллей: §f%d
 gregtech.universal.tooltip.working_area=§bРабочая область: §f%,dx%,d
-gregtech.universal.tooltip.working_area_max=§bМаксимальная рабочая область: §f%,dx%,d
+gregtech.universal.tooltip.working_area_max=§bМакс. рабочая область: §f%,dx%,d
 gregtech.universal.tooltip.working_area_chunks_max=§bMax Working Area: §f%,dx%,d Chunks
 gregtech.universal.tooltip.uses_per_tick=Потребляет §f%,d EU/т §7когда работает
 gregtech.universal.tooltip.uses_per_tick_steam=Потребляет §f%,d Л/т §7 Пара когда работает
@@ -4810,7 +4809,7 @@ gregtech.recipe.computation_per_tick=Мин. Вычисления: %,d CWU/t
 gregtech.fluid.click_to_fill=§7Нажмите с хралищем для жидкости, чтобы §bзаполнить §7резервуар.
 gregtech.fluid.click_to_empty=§7Нажмите с хралищем для жидкости, чтобы §cопустошить §7резервуар.
 gregtech.fluid.click_combined=§7Нажмите с хралищем для жидкости, чтобы §bзаполнить §7или §cопустошить §7резервуар.
-gregtech.tool_action.show_tooltips=Зажмите SHIFT для просмотра информации Инструмента
+gregtech.tool_action.show_tooltips=Зажмите SHIFT для Информации о Инструменте
 gregtech.tool_action.screwdriver.auto_output=§8Используйте отвертку, чтобы переключить Авто-Вывод
 gregtech.tool_action.screwdriver.toggle_mode_covers=§8Используйте отвертку, чтобы переключить Режим или настроить улучшения
 gregtech.tool_action.screwdriver.access_covers=§8Используйте отвертку, чтобы настроить улучшения
@@ -4829,8 +4828,8 @@ gregtech.tool_action.tape=§8Используйте Клейкую Ленту д
 gregtech.fluid.generic=%s
 gregtech.fluid.plasma=Плазма (%s)
 gregtech.fluid.empty=Пустой
-gregtech.fluid.amount=§7%,d/%,d
-gregtech.fluid.temperature=§7Температура: %dK
+gregtech.fluid.amount=§9Количество: %,d/%,d Л
+gregtech.fluid.temperature=§cТемпература: %dK
 gregtech.fluid.temperature.cryogenic=§bКриогенный! Соблюдайте осторожность!
 gregtech.fluid.state_gas=§7Состояние: Газообразный
 gregtech.fluid.state_liquid=§7Состояние: Жидкость
@@ -4838,7 +4837,7 @@ gregtech.fluid.state_plasma=§7Состояние: Плазма
 gregtech.fluid.type_acid.tooltip=§6Кислота! Соблюдайте осторожность!
 gregtech.gui.fuel_amount=Кол. топлива:
 gregtech.gui.fluid_amount=Кол. жидкости:
-gregtech.gui.amount_raw=Количество:
+gregtech.gui.amount_raw=Кол-во:
 gregtech.gui.toggle_view.disabled=Переключить вид (Жидкости)
 gregtech.gui.toggle_view.enabled=Переключить вид (Предметы)
 gregtech.gui.overclock.enabled=Ускорение включено./nНажмите, чтобы отключить
@@ -4851,7 +4850,7 @@ gregtech.gui.fluid_auto_output.tooltip.disabled=Авто. вывод жидко
 gregtech.gui.item_auto_output.tooltip.enabled=Авто. вывод предметов включен
 gregtech.gui.item_auto_output.tooltip.disabled=Авто. вывод предметов отключен
 gregtech.gui.charger_slot.tooltip=§fСлот двухстороннего питания§r/n§7Может потреблять энергию от %s §7аккумуляторов либо-же отдавать
-gregtech.gui.configurator_slot.tooltip=§fСлот Конфигурации§r/n§aУстановить Значение: §f%d§7/n/n§7ЛКМ/ПКМ/Прокрутка для переключения по листу/n§7Shift-ПКМ для очистка
+gregtech.gui.configurator_slot.tooltip=§fСлот Конфигурации§r\n§aУстановить Значение: §f%d§7\n\n§7ЛКМ/ПКМ/Прокрутка для переключения по листу\n§7Shift-ЛКМ чтобы выбрать селектор\n§7Shift-ПКМ для очистка
 gregtech.gui.fluid_lock.tooltip.enabled=Блокировка жидкости включена
 gregtech.gui.fluid_lock.tooltip.disabled=Блокировка жидкости выключена
 gregtech.gui.fluid_voiding.tooltip.enabled=Удаление избытков жидкости Включен
@@ -4866,7 +4865,7 @@ gregtech.gui.me_network.offline=Статус Сети: §2Не в Сети§r
 gregtech.gui.waiting_list=Очередь на отправку:
 gregtech.gui.config_slot=§fНастройка§r
 gregtech.gui.config_slot.set=§7Нажмите для §bвыбора/настройкиt§7 слота.§r
-gregtech.gui.config_slot.scroll=§7Колесо мыши §aизменяет§7 количество.§r
+gregtech.gui.config_slot.scroll=§7Колесо мыши §aизменяет§7 кол-во.§r
 gregtech.gui.config_slot.remove=§7Правая кнопка для §4очистки§7 слота настройки.§r
 ore.spawnlocation.name=Информация о появлении руды
 gregtech.jei.ore.surface_rock_1=Поверхностные залежи с этим материалом обозначают места появления жил.
@@ -4899,8 +4898,8 @@ fluid.spawnlocation.name=Информация о жидкостном место
 
 
 gregtech.jei.materials.average_mass=Средняя масса: %,d
-gregtech.jei.materials.average_neutrons=Среднее количество нейтронов: %,d
-gregtech.jei.materials.average_protons=Среднее количество протонов: %,d
+gregtech.jei.materials.average_neutrons=Ср. кол-во нейтронов: %,d
+gregtech.jei.materials.average_protons=Ср. кол-во протонов: %,d
 gregtech.item_filter.empty_item=Пусто (нет предмета)
 gregtech.item_filter.footer=§eНажмите с предметом, чтобы переопределить
 gregtech.cable.voltage=§aВольтаж: §f%,d §f(%s§f)
@@ -4924,12 +4923,12 @@ gregtech.multiblock.not_enough_energy_output=§eСовет:§f Энергети
 gregtech.multiblock.progress=Прогресс: %s%%
 gregtech.multiblock.invalid_structure=Неверная структура.
 gregtech.multiblock.invalid_structure.tooltip=Этот блок является контроллером многоблочной структуры. для получения справки по созданию см. шаблон структуры в JEI.
-gregtech.multiblock.validation_failed=Неверное количество входов/выходов.
+gregtech.multiblock.validation_failed=Неверное кол-во входов/выходов.
 gregtech.multiblock.max_energy_per_tick=Макс. EU/т: §a%s (%s§r)
 gregtech.multiblock.generation_eu=Генерирует:§a%s EU/t
 gregtech.multiblock.universal.no_problems=Механизм работает исправно!
 gregtech.multiblock.universal.has_problems=Механизм неисправен!
-gregtech.multiblock.universal.has_problems_header=Исправьте следующие проблемы в люке обслуживания:
+gregtech.multiblock.universal.has_problems_header=Исправьте следующие проблемы в Люке обслуживания:
 gregtech.multiblock.universal.problem.wrench=%s§7Труба расшатана. (§aКлюч§7)
 gregtech.multiblock.universal.problem.screwdriver=%s§7Винты не закручены. (§aОтвертка§7)
 gregtech.multiblock.universal.problem.soft_mallet=%s§7Что-то заклинило. (§aКиянка§7)
@@ -4941,7 +4940,7 @@ gregtech.multiblock.universal.distinct_enabled=Раздельные люки: §
 gregtech.multiblock.universal.distinct_disabled=Раздельные люки: §aВsключено§r/nКаждый входной люк будет рассматриваться как комбинированный ввод для поиска рецепта.
 gregtech.multiblock.universal.distinct_not_supported=Эту структура не поддерживает Раздельные Люки
 gregtech.multiblock.universal.no_flex_button=Эта структура не имеет дополнительных функций с этой кнопкой.
-gregtech.multiblock.parallel=Параллельное выполнение до %d рецептов
+gregtech.multiblock.parallel=Макс. Параллелей: %s
 gregtech.multiblock.multiple_recipemaps.header=Режим машины:
 gregtech.multiblock.multiple_recipemaps.tooltip=Нажмите отвёрткой по контроллеру, чтобы изменить режим машины.
 gregtech.multiblock.multiple_recipemaps_recipes.tooltip=Режим машин: §e%s§r
@@ -4971,9 +4970,9 @@ gregtech.multiblock.blast_furnace.max_temperature=Максимальная те
 gregtech.multiblock.multi_furnace.heating_coil_discount=EU усиление нагревательной катушки: %sx
 gregtech.multiblock.distillation_tower.distilling_fluid=Дистилляция %s
 gregtech.multiblock.large_combustion_engine.no_lubricant=Нет Смазки.
-gregtech.multiblock.large_combustion_engine.lubricant_amount=Количество смазки: %smB
-gregtech.multiblock.large_combustion_engine.oxygen_amount=Количество кислорода: %smB
-gregtech.multiblock.large_combustion_engine.liquid_oxygen_amount=Количество жидкого кислорода: %smB
+gregtech.multiblock.large_combustion_engine.lubricant_amount=Кол-во смазки: %smB
+gregtech.multiblock.large_combustion_engine.oxygen_amount=Кол-во кислорода: %smB
+gregtech.multiblock.large_combustion_engine.liquid_oxygen_amount=Кол-во жид. кислорода: %smB
 gregtech.multiblock.large_combustion_engine.oxygen_boosted=§bКислород добавлен.
 gregtech.multiblock.large_combustion_engine.liquid_oxygen_boosted=§bЖидкий кислород добавлен.
 gregtech.multiblock.large_combustion_engine.supply_oxygen_to_boost=Принимает кислород для ускорения.
@@ -4981,11 +4980,11 @@ gregtech.multiblock.large_combustion_engine.supply_liquid_oxygen_to_boost=При
 gregtech.multiblock.large_combustion_engine.obstructed=Что-то мешает воздухозаборнику двигателя.
 gregtech.multiblock.turbine.fuel_amount=Топлива: %smB (%s)
 gregtech.multiblock.turbine.rotor_speed=Скорость: %s/%s об/мин
-gregtech.multiblock.turbine.rotor_durability=Прочность ротора: %s%%
+gregtech.multiblock.turbine.rotor_durability=Прочность ротора: %s
 gregtech.multiblock.turbine.rotor_durability_low=Прочность ротора низкая!
 gregtech.multiblock.turbine.no_rotor=Нет Ротора в Держателе ротора.
 gregtech.multiblock.turbine.fuel_needed=Потребляет %s за %s тиков
-gregtech.multiblock.turbine.efficiency=Эффективность турбины: %s%%
+gregtech.multiblock.turbine.efficiency=Эффективность турбины: %s
 gregtech.multiblock.turbine.energy_per_tick=Выход: %s/%s EU/t
 gregtech.multiblock.turbine.obstructed=Поверхность турбины заблокирована
 gregtech.multiblock.turbine.efficiency_tooltip=Каждый держатель ротора выше %s§7 добавляет §f10%% эффективности§7.
@@ -5071,7 +5070,7 @@ gregtech.command.copy.click_to_copy=Нажмите для копирования
 gregtech.command.copy.copied_start=Скопировано [
 gregtech.command.copy.copied_end=] в буфер обмена
 gregtech.chat.cape=§5Поздравляю: вы только что разблокировали новый плащ! Откройте в терминале Переключатель плащей для подробной информации.§r
-gregtech.universal.clear_nbt_recipe.tooltip=§cЭто уничтожить весь контент!
+gregtech.universal.clear_nbt_recipe.tooltip=§cЭто уничтожит весь контент!
 gregtech.cover.detector_base.message_inverted_state=Статус детектора: Инвертированный
 gregtech.cover.detector_base.message_normal_state=Статус детектора: Обычный
 gregtech.creative.chest.item=Предмет
@@ -5901,3 +5900,10 @@ for.bees.species.fluorine=Фторовая
 
 # Bee Descriptions (many more to do)
 for.bees.description.clay=Органическая пчела, известная своей трудолюбивостью и усердием. Ходят слухи, что их можно найти грязь в нужном биоме.|Пчеловодство 101
+gregtech.gui.item_auto_collapse.tooltip.disabled=Авто-совмещение предметов выключено
+gregtech.gui.item_auto_collapse.tooltip.enabled=Авто-совмещение предметов включено
+gregtech.gui.configurator_slot.unavailable.tooltip=Слот Интегральной схемы недоступен
+cover.ore_dictionary_filter.button.case_sensitive.enabled=С учетом регистра
+cover.ore_dictionary_filter.button.match_all.disabled=Любая из записей из Словаря руд
+cover.ore_dictionary_filter.button.match_all.enabled=Все записи из Словаря руд
+cover.ore_dictionary_filter.button.case_sensitive.disabled=Без учета регистра
diff --git a/src/main/resources/assets/gregtech/lang/zh_cn.lang b/src/main/resources/assets/gregtech/lang/zh_cn.lang
index 88906474bcf..67437657210 100644
--- a/src/main/resources/assets/gregtech/lang/zh_cn.lang
+++ b/src/main/resources/assets/gregtech/lang/zh_cn.lang
@@ -112,7 +112,7 @@ gregtech.waila.progress_computation=计算进度:%s / %s
 
 gregtech.multiblock.title=多方块结构
 gregtech.multiblock.primitive_blast_furnace.bronze.description=土高炉是(PBF)是个多方块结构。尽管它不是很快,却能在游戏前期为你的发展提供钢材。
-gregtech.multiblock.coke_oven.description=焦炉是个多方块结构,用于在早期生产焦炭和杂酚油。无需燃料即可工作,内部至多可容纳32桶杂酚油。其存储可通过焦炉仓进行访问。
+gregtech.multiblock.coke_oven.description=焦炉是一种多方块结构,用于在早期生产焦煤和杂酚油,无需燃料即可工作,内部至多可容纳 32 桶杂酚油。其存储可通过焦炉仓进行访问。
 gregtech.multiblock.vacuum_freezer.description=真空冷冻机是个多方块结构,主要用于热锭冷却。此外,它还可以冻结水等其他物质。
 gregtech.multiblock.implosion_compressor.description=聚爆压缩机是个多方块结构,能够借助炸药将宝石粉转化为相应的宝石。
 gregtech.multiblock.pyrolyse_oven.description=热解炉是种用于将原木处理为木炭、杂酚油、灰烬或重油的多方块结构。
@@ -531,7 +531,7 @@ metaitem.tool.datamodule.name=数据模块
 metaitem.tool.datamodule.tooltip=超复杂数据存储/n§c只能用数据库读取
 metaitem.circuit.integrated.name=编程电路
 metaitem.circuit.integrated.tooltip=右击以打开配置界面/n/n手持并潜行右击带有编程电路槽位的机器可应用相应配置。/n
-metaitem.circuit.integrated.gui=编程电路配置
+metaitem.circuit.integrated.gui=电路配置
 metaitem.circuit.integrated.jei_description=JEI将仅显示匹配当前编程电路配置的配方。\n\n在编程电路配置界面中调整配置以查看对应配方。
 
 item.glass.lens=玻璃透镜(白色)
@@ -852,7 +852,7 @@ metaitem.cover.item.voiding.advanced.tooltip=作§f覆盖板§7时允许按数
 metaitem.cover.storage.name=存储覆盖板
 metaitem.cover.storage.tooltip=给零碎的小玩意准备的小型存储空间
 metaitem.cover.maintenance.detector.name=维护需求覆盖板
-metaitem.cover.maintenance.detector.tooltip=作§f覆盖板§7时在机器§f需要维护§7输出红石信号。
+metaitem.cover.maintenance.detector.tooltip=作§f覆盖板§7时在机器§f需要维护§7时发出红石信号。
 
 metaitem.cover.facade.name=%s伪装板
 metaitem.cover.facade.tooltip=可作为§f覆盖板§7加装装饰性套壳。
@@ -924,7 +924,7 @@ metaitem.wooden_form.brick.name=木制砖模具
 
 item.gt.tool.replace_tool_head=在合成栏用新的工具头替换
 item.gt.tool.usable_as=可用作:§f%s
-item.gt.tool.behavior.silk_ice=§b切冰利刃:§f精准采集冰
+item.gt.tool.behavior.silk_ice=§b切冰利刃:§f精准采集冰块
 item.gt.tool.behavior.torch_place=§e洞窟探客:§f右击可放置火把
 item.gt.tool.behavior.tree_felling=§4伐木好手:§f一次性砍下整棵树木
 item.gt.tool.behavior.shield_disable=§c野兽蛮攻:§f破除盾牌
@@ -1008,6 +1008,9 @@ item.gt.tool.screwdriver_lv.name=%s螺丝刀(LV)
 item.gt.tool.screwdriver_lv.tooltip=§8调整覆盖板和机器
 item.gt.tool.plunger.name=%s搋子
 item.gt.tool.plunger.tooltip=§8从机器中抽除流体
+item.gt.tool.wire_cutter_lv.name=%s剪线钳(LV)
+item.gt.tool.wire_cutter_hv.name=%s剪线钳(HV)
+item.gt.tool.wire_cutter_iv.name=%s剪线钳(IV)
 
 
 item.gt.tool.tooltip.crafting_uses=§a合成耐久度:%s
@@ -1175,7 +1178,7 @@ cover.filter.blacklist.disabled=白名单
 cover.filter.blacklist.enabled=黑名单
 
 cover.ore_dictionary_filter.title=矿物词典过滤
-cover.ore_dictionary_filter.info=§b接受复杂表达式/n§6a & b§r = 且/n§6a | b§r = 或/n§6a ^ b§r = 异或/n§6! abc§r = 非/n§6( abc )§r 表示组别/n§6*§r 表示通配(也即零或多个字符)/n§6?§r 表示任意一个字符/n§6()§r 匹配空条目(包括不带矿物词典的物品)/n在表达式开头添加 §6$c§r 可严格区分大小写/n§b使用范例:/n§6dust*Gold | (plate* & !*Double*)/n匹配双层板以外的板与包括小撮、小堆在内的所有金粉
+cover.ore_dictionary_filter.info=§b接受复杂表达式/n§6a & b§r = 且/n§6a | b§r = 或/n§6a ^ b§r = 异或/n§6! abc§r = 非/n§6( abc )§r 表示组别/n§6*§r 表示通配(也即零或多个字符)/n§6?§r 表示任意一个字符/n§6()§r 匹配空条目(包括不带矿物词典的物品)/n在表达式开头添加 §b使用范例:/n§6dust*Gold | (plate* & !*Double*)/n匹配双层板以外的板与包括小撮、小堆在内的所有金粉
 cover.ore_dictionary_filter.test_slot.info=放入一件物品以测试是否匹配过滤表达式
 cover.ore_dictionary_filter.test_slot.matches=§a* %s
 cover.ore_dictionary_filter.test_slot.matches_not=§c* %s
@@ -1186,6 +1189,10 @@ cover.ore_dictionary_filter.status.err_warn=§c%s个错误、%s个警告
 cover.ore_dictionary_filter.status.warn=§7%s个警告
 cover.ore_dictionary_filter.status.no_issues=§a无问题
 cover.ore_dictionary_filter.status.explain=矿物过滤说明:
+cover.ore_dictionary_filter.button.case_sensitive.disabled=匹配大小写
+cover.ore_dictionary_filter.button.case_sensitive.enabled=忽略大小写
+cover.ore_dictionary_filter.button.match_all.disabled=匹配物品的任一矿词条目
+cover.ore_dictionary_filter.button.match_all.enabled=匹配物品的所有矿词条目
 
 cover.ore_dictionary_filter.preview.next=...接着是
 cover.ore_dictionary_filter.preview.match='%s'
@@ -1300,11 +1307,6 @@ cover.fluid_regulator.transfer_mode.description=§e任意传输§r - 在此模
 cover.fluid_regulator.supply_exact=精确补给:%s
 cover.fluid_regulator.keep_exact=保持补给:%s
 
-cover.machine_controller.title=机器控制设定
-cover.machine_controller.normal=普通
-cover.machine_controller.inverted=反相
-cover.machine_controller.inverted.description=§e普通§r - 该模式下的覆盖板需要比设定强度小的红石信号来触发/n§e反相§r - 该模式下的覆盖板需要比设定强度大的红石信号来触发
-cover.machine_controller.redstone=最小红石信号强度:%,d
 cover.machine_controller.mode.machine=控制目标:机器
 cover.machine_controller.mode.cover_up=控制目标:覆盖板(顶面)
 cover.machine_controller.mode.cover_down=控制目标:覆盖板(底面)
@@ -1312,6 +1314,12 @@ cover.machine_controller.mode.cover_south=控制目标:覆盖板(南面)
 cover.machine_controller.mode.cover_north=控制目标:覆盖板(北面)
 cover.machine_controller.mode.cover_east=控制目标:覆盖板(东面)
 cover.machine_controller.mode.cover_west=控制目标:覆盖板(西面)
+cover.machine_controller.this_cover=§c此覆盖板
+cover.machine_controller.cover_not_controllable=§c不可控制的覆盖板
+cover.machine_controller.machine_not_controllable=§c不可控制的机器
+cover.machine_controller.control=控制:
+cover.machine_controller.enable_with_redstone=接收红石信号时开启
+cover.machine_controller.disable_with_redstone=接收红石信号时关闭
 
 cover.ender_fluid_link.title=末影流体连接
 cover.ender_fluid_link.iomode.enabled=已启用I/O
@@ -1356,9 +1364,9 @@ item.material.oreprefix.oreBasalt=玄武岩%s矿石
 item.material.oreprefix.oreSand=沙子%s矿石
 item.material.oreprefix.oreRedSand=红沙%s矿石
 item.material.oreprefix.oreNetherrack=地狱岩%s矿石
-item.material.oreprefix.oreNether=下界%s矿石
+item.material.oreprefix.oreNether=地狱岩%s矿石
 item.material.oreprefix.oreEndstone=末地石%s矿石
-item.material.oreprefix.oreEnd=末地%s矿石
+item.material.oreprefix.oreEnd=末地石%s矿石
 item.material.oreprefix.ore=%s矿石
 item.material.oreprefix.oreGranite=花岗岩%s矿石
 item.material.oreprefix.oreDiorite=闪长岩%s矿石
@@ -1645,7 +1653,7 @@ gregtech.material.rose_gold=玫瑰金
 gregtech.material.black_bronze=黑青铜
 gregtech.material.bismuth_bronze=铋青铜
 gregtech.material.biotite=黑云母
-gregtech.material.powellite=钼钨钙矿
+gregtech.material.powellite=钼钙矿
 gregtech.material.pyrite=黄铁矿
 gregtech.material.pyrolusite=软锰矿
 gregtech.material.pyrope=镁铝榴石
@@ -2343,6 +2351,17 @@ tile.treated_wood_fence.name=防腐木栅栏
 tile.rubber_wood_fence_gate.name=橡胶木栅栏门
 tile.treated_wood_fence_gate.name=防腐木栅栏门
 
+tile.gt_explosive.breaking_tooltip=破坏它会引爆火药,潜行挖掘以重新拾取
+tile.gt_explosive.lighting_tooltip=无法用红石信号引爆
+
+tile.powderbarrel.name=火药桶
+tile.powderbarrel.drops_tooltip=爆炸范围略大于TNT,所有被摧毁的方块都会掉落
+entity.Powderbarrel.name=火药桶
+
+tile.itnt.name=工业TNT
+tile.itnt.drops_tooltip=爆炸范围比TNT大得多,所有被摧毁的方块都会掉落
+entity.ITNT.name=工业TNT
+
 tile.brittle_charcoal.name=脆木炭块
 tile.brittle_charcoal.tooltip.1=产自木炭堆点火器。
 tile.brittle_charcoal.tooltip.2=采掘可掉落木炭。
@@ -2908,9 +2927,9 @@ gregtech.machine.steam_hammer_bronze.tooltip=锤锻机器
 gregtech.machine.steam_hammer_steel.name=高压蒸汽锻造锤
 gregtech.machine.steam_hammer_steel.tooltip=锤锻机器
 gregtech.machine.steam_furnace_bronze.name=蒸汽熔炉
-gregtech.machine.steam_furnace_bronze.tooltip=利用蒸汽来熔炼物品
+gregtech.machine.steam_furnace_bronze.tooltip=利用蒸汽冶炼物品
 gregtech.machine.steam_furnace_steel.name=高压蒸汽熔炉
-gregtech.machine.steam_furnace_steel.tooltip=利用蒸汽来熔炼物品
+gregtech.machine.steam_furnace_steel.tooltip=利用蒸汽冶炼物品
 gregtech.machine.steam_alloy_smelter_bronze.name=蒸汽合金炉
 gregtech.machine.steam_alloy_smelter_bronze.tooltip=熔合冶炼炉
 gregtech.machine.steam_alloy_smelter_steel.name=高压合金炉
@@ -2989,9 +3008,9 @@ gregtech.machine.macerator.lv.tooltip=粉碎矿石
 gregtech.machine.macerator.mv.name=进阶研磨机
 gregtech.machine.macerator.mv.tooltip=粉碎矿石
 gregtech.machine.macerator.hv.name=进阶研磨机 II
-gregtech.machine.macerator.hv.tooltip=粉碎矿石,且能产出副产物
+gregtech.machine.macerator.hv.tooltip=粉碎矿石并获得副产物
 gregtech.machine.macerator.ev.name=进阶研磨机 III
-gregtech.machine.macerator.ev.tooltip=粉碎矿石,且能产出副产物
+gregtech.machine.macerator.ev.tooltip=粉碎矿石并获得副产物
 gregtech.machine.macerator.iv.name=精英研磨机
 gregtech.machine.macerator.iv.tooltip=全自动破壁机 9001
 gregtech.machine.macerator.luv.name=精英研磨机 II
@@ -3049,11 +3068,11 @@ gregtech.machine.arc_furnace.hv.tooltip=谁还要高炉啊?
 gregtech.machine.arc_furnace.ev.name=进阶电弧炉 III
 gregtech.machine.arc_furnace.ev.tooltip=谁还要高炉啊?
 gregtech.machine.arc_furnace.iv.name=精英电弧炉
-gregtech.machine.arc_furnace.iv.tooltip=解体加热器
+gregtech.machine.arc_furnace.iv.tooltip=放电加热器
 gregtech.machine.arc_furnace.luv.name=精英电弧炉 II
-gregtech.machine.arc_furnace.luv.tooltip=解体加热器
+gregtech.machine.arc_furnace.luv.tooltip=放电加热器
 gregtech.machine.arc_furnace.zpm.name=精英电弧炉 III
-gregtech.machine.arc_furnace.zpm.tooltip=解体加热器
+gregtech.machine.arc_furnace.zpm.tooltip=放电加热器
 gregtech.machine.arc_furnace.uv.name=终极电弧炉
 gregtech.machine.arc_furnace.uv.tooltip=短路加热器
 gregtech.machine.arc_furnace.uhv.name=史诗电弧炉
@@ -3275,23 +3294,23 @@ gregtech.machine.chemical_reactor.hv.tooltip=使化学品相互反应
 gregtech.machine.chemical_reactor.ev.name=进阶化学反应釜 III
 gregtech.machine.chemical_reactor.ev.tooltip=使化学品相互反应
 gregtech.machine.chemical_reactor.iv.name=精英化学反应釜
-gregtech.machine.chemical_reactor.iv.tooltip=化学反应操作仪
+gregtech.machine.chemical_reactor.iv.tooltip=化学表演艺术家
 gregtech.machine.chemical_reactor.luv.name=精英化学反应釜 II
-gregtech.machine.chemical_reactor.luv.tooltip=化学反应操作仪
+gregtech.machine.chemical_reactor.luv.tooltip=化学表演艺术家
 gregtech.machine.chemical_reactor.zpm.name=精英化学反应釜 III
-gregtech.machine.chemical_reactor.zpm.tooltip=化学反应操作仪
+gregtech.machine.chemical_reactor.zpm.tooltip=化学表演艺术家
 gregtech.machine.chemical_reactor.uv.name=终极化学反应釜
-gregtech.machine.chemical_reactor.uv.tooltip=化学反应催化者
+gregtech.machine.chemical_reactor.uv.tooltip=反应催化器
 gregtech.machine.chemical_reactor.uhv.name=史诗化学反应釜
-gregtech.machine.chemical_reactor.uhv.tooltip=化学反应催化者
+gregtech.machine.chemical_reactor.uhv.tooltip=反应催化器
 gregtech.machine.chemical_reactor.uev.name=史诗化学反应釜 II
-gregtech.machine.chemical_reactor.uev.tooltip=化学反应催化者
+gregtech.machine.chemical_reactor.uev.tooltip=反应催化器
 gregtech.machine.chemical_reactor.uiv.name=史诗化学反应釜 III
-gregtech.machine.chemical_reactor.uiv.tooltip=化学反应催化者
+gregtech.machine.chemical_reactor.uiv.tooltip=反应催化器
 gregtech.machine.chemical_reactor.uxv.name=史诗化学反应釜 IV
-gregtech.machine.chemical_reactor.uxv.tooltip=化学反应催化者
+gregtech.machine.chemical_reactor.uxv.tooltip=反应催化器
 gregtech.machine.chemical_reactor.opv.name=传奇化学反应釜
-gregtech.machine.chemical_reactor.opv.tooltip=化学反应催化者
+gregtech.machine.chemical_reactor.opv.tooltip=反应催化器
 
 # Compressor
 gregtech.machine.compressor.lv.name=基础压缩机
@@ -3337,17 +3356,17 @@ gregtech.machine.cutter.luv.tooltip=物质切削器
 gregtech.machine.cutter.zpm.name=精英切割机 III
 gregtech.machine.cutter.zpm.tooltip=物质切削器
 gregtech.machine.cutter.uv.name=终极切割机
-gregtech.machine.cutter.uv.tooltip=物品对象分割者
+gregtech.machine.cutter.uv.tooltip=对象分割者
 gregtech.machine.cutter.uhv.name=史诗切割机
-gregtech.machine.cutter.uhv.tooltip=物品对象分割者
+gregtech.machine.cutter.uhv.tooltip=对象分割者
 gregtech.machine.cutter.uev.name=史诗切割机 II
-gregtech.machine.cutter.uev.tooltip=物品对象分割者
+gregtech.machine.cutter.uev.tooltip=对象分割者
 gregtech.machine.cutter.uiv.name=史诗切割机 III
-gregtech.machine.cutter.uiv.tooltip=物品对象分割者
+gregtech.machine.cutter.uiv.tooltip=对象分割者
 gregtech.machine.cutter.uxv.name=史诗切割机 IV
-gregtech.machine.cutter.uxv.tooltip=物品对象分割者
+gregtech.machine.cutter.uxv.tooltip=对象分割者
 gregtech.machine.cutter.opv.name=传奇切割机
-gregtech.machine.cutter.opv.tooltip=物品对象分割者
+gregtech.machine.cutter.opv.tooltip=对象分割者
 
 # Distillery
 gregtech.machine.distillery.lv.name=基础蒸馏室
@@ -3365,17 +3384,17 @@ gregtech.machine.distillery.luv.tooltip=凝结物质分离器
 gregtech.machine.distillery.zpm.name=精英蒸馏室 III
 gregtech.machine.distillery.zpm.tooltip=凝结物质分离器
 gregtech.machine.distillery.uv.name=终极蒸馏室
-gregtech.machine.distillery.uv.tooltip=少数分流者
+gregtech.machine.distillery.uv.tooltip=馏分分离器
 gregtech.machine.distillery.uhv.name=史诗蒸馏室
-gregtech.machine.distillery.uhv.tooltip=少数分流者
+gregtech.machine.distillery.uhv.tooltip=馏分分离器
 gregtech.machine.distillery.uev.name=史诗蒸馏室 II
-gregtech.machine.distillery.uev.tooltip=少数分流者
+gregtech.machine.distillery.uev.tooltip=馏分分离器
 gregtech.machine.distillery.uiv.name=史诗蒸馏室 III
-gregtech.machine.distillery.uiv.tooltip=少数分流者
+gregtech.machine.distillery.uiv.tooltip=馏分分离器
 gregtech.machine.distillery.uxv.name=史诗蒸馏室 IV
-gregtech.machine.distillery.uxv.tooltip=少数分流者
+gregtech.machine.distillery.uxv.tooltip=馏分分离器
 gregtech.machine.distillery.opv.name=传奇蒸馏室
-gregtech.machine.distillery.opv.tooltip=少数分流者
+gregtech.machine.distillery.opv.tooltip=馏分分离器
 
 # Electrolyzer
 gregtech.machine.electrolyzer.lv.name=基础电解机
@@ -3435,13 +3454,13 @@ gregtech.machine.electromagnetic_separator.opv.tooltip=电磁场驱离装置
 
 # Extractor
 gregtech.machine.extractor.lv.name=基础提取机
-gregtech.machine.extractor.lv.tooltip=毁灭级榨干机 - D123
+gregtech.machine.extractor.lv.tooltip=毁灭级榨汁机 - D123
 gregtech.machine.extractor.mv.name=进阶提取机
-gregtech.machine.extractor.mv.tooltip=毁灭级榨干机 - D123
+gregtech.machine.extractor.mv.tooltip=毁灭级榨汁机 - D123
 gregtech.machine.extractor.hv.name=进阶提取机 II
-gregtech.machine.extractor.hv.tooltip=毁灭级榨干机 - D123
+gregtech.machine.extractor.hv.tooltip=毁灭级榨汁机 - D123
 gregtech.machine.extractor.ev.name=进阶提取机 III
-gregtech.machine.extractor.ev.tooltip=毁灭级榨干机 - D123
+gregtech.machine.extractor.ev.tooltip=毁灭级榨汁机 - D123
 gregtech.machine.extractor.iv.name=精英提取机
 gregtech.machine.extractor.iv.tooltip=真空提炼机
 gregtech.machine.extractor.luv.name=精英提取机 II
@@ -3463,13 +3482,13 @@ gregtech.machine.extractor.opv.tooltip=液化吸取者
 
 # Extruder
 gregtech.machine.extruder.lv.name=基础压模器
-gregtech.machine.extruder.lv.tooltip=通用的金属处理机器
+gregtech.machine.extruder.lv.tooltip=通用型金属加工器
 gregtech.machine.extruder.mv.name=进阶压模器
-gregtech.machine.extruder.mv.tooltip=通用的金属处理机器
+gregtech.machine.extruder.mv.tooltip=通用型金属加工器
 gregtech.machine.extruder.hv.name=进阶压模器 II
-gregtech.machine.extruder.hv.tooltip=通用的金属处理机器
+gregtech.machine.extruder.hv.tooltip=通用型金属加工器
 gregtech.machine.extruder.ev.name=进阶压模器 III
-gregtech.machine.extruder.ev.tooltip=通用的金属处理机器
+gregtech.machine.extruder.ev.tooltip=通用型金属加工器
 gregtech.machine.extruder.iv.name=精英压模器
 gregtech.machine.extruder.iv.tooltip=材料压出器
 gregtech.machine.extruder.luv.name=精英压模器 II
@@ -3498,11 +3517,11 @@ gregtech.machine.fermenter.hv.tooltip=发酵流体
 gregtech.machine.fermenter.ev.name=进阶发酵槽 III
 gregtech.machine.fermenter.ev.tooltip=发酵流体
 gregtech.machine.fermenter.iv.name=精英发酵槽
-gregtech.machine.fermenter.iv.tooltip=发酵催促器
+gregtech.machine.fermenter.iv.tooltip=发酵加速器
 gregtech.machine.fermenter.luv.name=精英发酵槽 II
-gregtech.machine.fermenter.luv.tooltip=发酵催促器
+gregtech.machine.fermenter.luv.tooltip=发酵加速器
 gregtech.machine.fermenter.zpm.name=精英发酵槽 III
-gregtech.machine.fermenter.zpm.tooltip=发酵催促器
+gregtech.machine.fermenter.zpm.tooltip=发酵加速器
 gregtech.machine.fermenter.uv.name=终极发酵槽
 gregtech.machine.fermenter.uv.tooltip=呼吸控制器
 gregtech.machine.fermenter.uhv.name=史诗发酵槽
@@ -3518,41 +3537,41 @@ gregtech.machine.fermenter.opv.tooltip=呼吸控制器
 
 # Fluid Heater
 gregtech.machine.fluid_heater.lv.name=基础流体加热器
-gregtech.machine.fluid_heater.lv.tooltip=加热流体
+gregtech.machine.fluid_heater.lv.tooltip=加热你的流体
 gregtech.machine.fluid_heater.mv.name=进阶流体加热器
-gregtech.machine.fluid_heater.mv.tooltip=加热流体
+gregtech.machine.fluid_heater.mv.tooltip=加热你的流体
 gregtech.machine.fluid_heater.hv.name=进阶流体加热器 II
-gregtech.machine.fluid_heater.hv.tooltip=加热流体
+gregtech.machine.fluid_heater.hv.tooltip=加热你的流体
 gregtech.machine.fluid_heater.ev.name=进阶流体加热器 III
-gregtech.machine.fluid_heater.ev.tooltip=加热流体
+gregtech.machine.fluid_heater.ev.tooltip=加热你的流体
 gregtech.machine.fluid_heater.iv.name=精英流体加热器
-gregtech.machine.fluid_heater.iv.tooltip=热量注入器
+gregtech.machine.fluid_heater.iv.tooltip=热量灌注器
 gregtech.machine.fluid_heater.luv.name=精英流体加热器 II
-gregtech.machine.fluid_heater.luv.tooltip=热量注入器
+gregtech.machine.fluid_heater.luv.tooltip=热量灌注器
 gregtech.machine.fluid_heater.zpm.name=精英流体加热器 III
-gregtech.machine.fluid_heater.zpm.tooltip=热量注入器
+gregtech.machine.fluid_heater.zpm.tooltip=热量灌注器
 gregtech.machine.fluid_heater.uv.name=终极流体加热器
-gregtech.machine.fluid_heater.uv.tooltip=热力灌注者
+gregtech.machine.fluid_heater.uv.tooltip=热量灌输器
 gregtech.machine.fluid_heater.uhv.name=史诗流体加热器
-gregtech.machine.fluid_heater.uhv.tooltip=热力灌注者
+gregtech.machine.fluid_heater.uhv.tooltip=热量灌输器
 gregtech.machine.fluid_heater.uev.name=史诗流体加热器 II
-gregtech.machine.fluid_heater.uev.tooltip=热力灌注者
+gregtech.machine.fluid_heater.uev.tooltip=热量灌输器
 gregtech.machine.fluid_heater.uiv.name=史诗流体加热器 III
-gregtech.machine.fluid_heater.uiv.tooltip=热力灌注者
+gregtech.machine.fluid_heater.uiv.tooltip=热量灌输器
 gregtech.machine.fluid_heater.uxv.name=史诗流体加热器 IV
-gregtech.machine.fluid_heater.uxv.tooltip=热力灌注者
+gregtech.machine.fluid_heater.uxv.tooltip=热量灌输器
 gregtech.machine.fluid_heater.opv.name=传奇流体加热器
-gregtech.machine.fluid_heater.opv.tooltip=热力灌注者
+gregtech.machine.fluid_heater.opv.tooltip=热量灌输器
 
 # Fluid Solidifier
 gregtech.machine.fluid_solidifier.lv.name=基础流体固化器
-gregtech.machine.fluid_solidifier.lv.tooltip=冷却液体并定型为固体
+gregtech.machine.fluid_solidifier.lv.tooltip=冷却液体形成固体
 gregtech.machine.fluid_solidifier.mv.name=进阶流体固化器
-gregtech.machine.fluid_solidifier.mv.tooltip=冷却液体并定型为固体
+gregtech.machine.fluid_solidifier.mv.tooltip=冷却液体形成固体
 gregtech.machine.fluid_solidifier.hv.name=进阶流体固化器 II
-gregtech.machine.fluid_solidifier.hv.tooltip=冷却液体并定型为固体
+gregtech.machine.fluid_solidifier.hv.tooltip=冷却液体形成固体
 gregtech.machine.fluid_solidifier.ev.name=进阶流体固化器 III
-gregtech.machine.fluid_solidifier.ev.tooltip=冷却液体并定型为固体
+gregtech.machine.fluid_solidifier.ev.tooltip=冷却液体形成固体
 gregtech.machine.fluid_solidifier.iv.name=精英流体固化器
 gregtech.machine.fluid_solidifier.iv.tooltip=并不是制冰机
 gregtech.machine.fluid_solidifier.luv.name=精英流体固化器 II
@@ -3582,11 +3601,11 @@ gregtech.machine.forge_hammer.hv.tooltip=停,抡锤时间到!
 gregtech.machine.forge_hammer.ev.name=进阶锻造锤 III
 gregtech.machine.forge_hammer.ev.tooltip=停,抡锤时间到!
 gregtech.machine.forge_hammer.iv.name=精英锻造锤
-gregtech.machine.forge_hammer.iv.tooltip=板材锻造机
+gregtech.machine.forge_hammer.iv.tooltip=锻板机
 gregtech.machine.forge_hammer.luv.name=精英锻造锤 II
-gregtech.machine.forge_hammer.luv.tooltip=板材锻造机
+gregtech.machine.forge_hammer.luv.tooltip=锻板机
 gregtech.machine.forge_hammer.zpm.name=精英锻造锤 III
-gregtech.machine.forge_hammer.zpm.tooltip=板材锻造机
+gregtech.machine.forge_hammer.zpm.tooltip=锻板机
 gregtech.machine.forge_hammer.uv.name=终极锻造锤
 gregtech.machine.forge_hammer.uv.tooltip=冲击调制器
 gregtech.machine.forge_hammer.uhv.name=史诗锻造锤
@@ -3602,13 +3621,13 @@ gregtech.machine.forge_hammer.opv.tooltip=冲击调制器
 
 # Forming Press
 gregtech.machine.forming_press.lv.name=基础冲压机床
-gregtech.machine.forming_press.lv.tooltip=将印象压印为实际的存在
+gregtech.machine.forming_press.lv.tooltip=图像拓印者
 gregtech.machine.forming_press.mv.name=进阶冲压机床
-gregtech.machine.forming_press.mv.tooltip=印象具现者
+gregtech.machine.forming_press.mv.tooltip=图像拓印者
 gregtech.machine.forming_press.hv.name=进阶冲压机床 II
-gregtech.machine.forming_press.hv.tooltip=印象具现者
+gregtech.machine.forming_press.hv.tooltip=图像拓印者
 gregtech.machine.forming_press.ev.name=进阶冲压机床 III
-gregtech.machine.forming_press.ev.tooltip=印象具现者
+gregtech.machine.forming_press.ev.tooltip=图像拓印者
 gregtech.machine.forming_press.iv.name=精英冲压机床
 gregtech.machine.forming_press.iv.tooltip=对象层化机
 gregtech.machine.forming_press.luv.name=精英冲压机床 II
@@ -3742,13 +3761,13 @@ gregtech.machine.packer.opv.tooltip=亚马逊仓库
 
 # Polarizer
 gregtech.machine.polarizer.lv.name=基础两极磁化机
-gregtech.machine.polarizer.lv.tooltip=使磁体两极化
+gregtech.machine.polarizer.lv.tooltip=将你的磁体极化
 gregtech.machine.polarizer.mv.name=进阶两极磁化机
-gregtech.machine.polarizer.mv.tooltip=使磁体两极化
+gregtech.machine.polarizer.mv.tooltip=将你的磁体极化
 gregtech.machine.polarizer.hv.name=进阶两极磁化机 II
-gregtech.machine.polarizer.hv.tooltip=使磁体两极化
+gregtech.machine.polarizer.hv.tooltip=将你的磁体极化
 gregtech.machine.polarizer.ev.name=进阶两极磁化机 III
-gregtech.machine.polarizer.ev.tooltip=使磁体两极化
+gregtech.machine.polarizer.ev.tooltip=将你的磁体极化
 gregtech.machine.polarizer.iv.name=精英两极磁化机
 gregtech.machine.polarizer.iv.tooltip=磁性引入机
 gregtech.machine.polarizer.luv.name=精英两极磁化机 II
@@ -3778,11 +3797,11 @@ gregtech.machine.laser_engraver.hv.tooltip=请勿直视激光
 gregtech.machine.laser_engraver.ev.name=进阶精密激光蚀刻机 III
 gregtech.machine.laser_engraver.ev.tooltip=请勿直视激光
 gregtech.machine.laser_engraver.iv.name=精英精密激光蚀刻机
-gregtech.machine.laser_engraver.iv.tooltip=蕴含着两百零四万瓦的能量
+gregtech.machine.laser_engraver.iv.tooltip=功率高达两百零四万瓦
 gregtech.machine.laser_engraver.luv.name=精英精密激光蚀刻机 II
-gregtech.machine.laser_engraver.luv.tooltip=蕴含着两百零四万瓦的能量
+gregtech.machine.laser_engraver.luv.tooltip=功率高达八百一十六万瓦
 gregtech.machine.laser_engraver.zpm.name=精英精密激光蚀刻机 III
-gregtech.machine.laser_engraver.zpm.tooltip=蕴含着两百零四万瓦的能量
+gregtech.machine.laser_engraver.zpm.tooltip=功率高达三千两百六十四万瓦
 gregtech.machine.laser_engraver.uv.name=终极精密激光蚀刻机
 gregtech.machine.laser_engraver.uv.tooltip=高精度光子加农炮
 gregtech.machine.laser_engraver.uhv.name=史诗激光蚀刻机
@@ -3882,13 +3901,13 @@ gregtech.machine.wiremill.opv.tooltip=导线易形者
 
 # Circuit Assembler
 gregtech.machine.circuit_assembler.lv.name=基础电路组装机
-gregtech.machine.circuit_assembler.lv.tooltip=一拿一放,东拣西装。
+gregtech.machine.circuit_assembler.lv.tooltip=一拿一放,东拣西装
 gregtech.machine.circuit_assembler.mv.name=进阶电路组装机
-gregtech.machine.circuit_assembler.mv.tooltip=一拿一放,东拣西装。
+gregtech.machine.circuit_assembler.mv.tooltip=一拿一放,东拣西装
 gregtech.machine.circuit_assembler.hv.name=进阶电路组装机 II
-gregtech.machine.circuit_assembler.hv.tooltip=一拿一放,东拣西装。
+gregtech.machine.circuit_assembler.hv.tooltip=一拿一放,东拣西装
 gregtech.machine.circuit_assembler.ev.name=进阶电路组装机 III
-gregtech.machine.circuit_assembler.ev.tooltip=一拿一放,东拣西装。
+gregtech.machine.circuit_assembler.ev.tooltip=一拿一放,东拣西装
 gregtech.machine.circuit_assembler.iv.name=精英电路组装机
 gregtech.machine.circuit_assembler.iv.tooltip=电子厂
 gregtech.machine.circuit_assembler.luv.name=精英电路组装机 II
@@ -3896,17 +3915,17 @@ gregtech.machine.circuit_assembler.luv.tooltip=电子厂
 gregtech.machine.circuit_assembler.zpm.name=精英电路组装机 III
 gregtech.machine.circuit_assembler.zpm.tooltip=电子厂
 gregtech.machine.circuit_assembler.uv.name=终极电路组装机
-gregtech.machine.circuit_assembler.uv.tooltip=计算工厂
+gregtech.machine.circuit_assembler.uv.tooltip=计算机工厂
 gregtech.machine.circuit_assembler.uhv.name=史诗电路组装机
-gregtech.machine.circuit_assembler.uhv.tooltip=计算工厂
+gregtech.machine.circuit_assembler.uhv.tooltip=计算机工厂
 gregtech.machine.circuit_assembler.uev.name=史诗电路组装机 II
-gregtech.machine.circuit_assembler.uev.tooltip=计算工厂
+gregtech.machine.circuit_assembler.uev.tooltip=计算机工厂
 gregtech.machine.circuit_assembler.uiv.name=史诗电路组装机 III
-gregtech.machine.circuit_assembler.uiv.tooltip=计算工厂
+gregtech.machine.circuit_assembler.uiv.tooltip=计算机工厂
 gregtech.machine.circuit_assembler.uxv.name=史诗电路组装机 IV
-gregtech.machine.circuit_assembler.uxv.tooltip=计算工厂
+gregtech.machine.circuit_assembler.uxv.tooltip=计算机工厂
 gregtech.machine.circuit_assembler.opv.name=传奇电路组装机
-gregtech.machine.circuit_assembler.opv.tooltip=计算工厂
+gregtech.machine.circuit_assembler.opv.tooltip=计算机工厂
 
 # Mass Fabricator
 gregtech.machine.mass_fabricator.lv.name=基础质量发生器
@@ -3918,11 +3937,11 @@ gregtech.machine.mass_fabricator.hv.tooltip=UU物质 = “质量” * “发生
 gregtech.machine.mass_fabricator.ev.name=进阶质量发生器 III
 gregtech.machine.mass_fabricator.ev.tooltip=UU物质 = “质量” * “发生”的平方
 gregtech.machine.mass_fabricator.iv.name=精英质量发生器
-gregtech.machine.mass_fabricator.iv.tooltip=创世工坊
+gregtech.machine.mass_fabricator.iv.tooltip=创世纪工厂
 gregtech.machine.mass_fabricator.luv.name=精英质量发生器 II
-gregtech.machine.mass_fabricator.luv.tooltip=创世工坊
+gregtech.machine.mass_fabricator.luv.tooltip=创世纪工厂
 gregtech.machine.mass_fabricator.zpm.name=精英质量发生器 III
-gregtech.machine.mass_fabricator.zpm.tooltip=创世工坊
+gregtech.machine.mass_fabricator.zpm.tooltip=创世纪工厂
 gregtech.machine.mass_fabricator.uv.name=终极质量发生器
 gregtech.machine.mass_fabricator.uv.tooltip=存在之源
 gregtech.machine.mass_fabricator.uhv.name=史诗质量发生器
@@ -3938,13 +3957,13 @@ gregtech.machine.mass_fabricator.opv.tooltip=存在之源
 
 # Replicator
 gregtech.machine.replicator.lv.name=基础复制机
-gregtech.machine.replicator.lv.tooltip=生产出至纯的元素
+gregtech.machine.replicator.lv.tooltip=生产最纯净的元素
 gregtech.machine.replicator.mv.name=进阶复制机
-gregtech.machine.replicator.mv.tooltip=生产出至纯的元素
+gregtech.machine.replicator.mv.tooltip=生产最纯净的元素
 gregtech.machine.replicator.hv.name=进阶复制机 II
-gregtech.machine.replicator.hv.tooltip=生产出至纯的元素
+gregtech.machine.replicator.hv.tooltip=生产最纯净的元素
 gregtech.machine.replicator.ev.name=进阶复制机 III
-gregtech.machine.replicator.ev.tooltip=生产出至纯的元素
+gregtech.machine.replicator.ev.tooltip=生产最纯净的元素
 gregtech.machine.replicator.iv.name=精英复制机
 gregtech.machine.replicator.iv.tooltip=物质粘贴机
 gregtech.machine.replicator.luv.name=精英复制机 II
@@ -4146,7 +4165,7 @@ gregtech.machine.transformer.adjustable.opv.name=过载压高能变压器(§9O
 gregtech.machine.diode.message=电流吞吐上限:%s
 gregtech.machine.diode.tooltip_tool_usage=手持软锤右击以调节电流。
 gregtech.machine.diode.tooltip_general=使能量作单向传输并限制电流
-gregtech.machine.diode.tooltip_starts_at=默认为§f1A§7,使用软锤来轮换电流设定
+gregtech.machine.diode.tooltip_starts_at=默认允许§f1A§r电流通行,使用软锤切换。
 
 gregtech.machine.diode.ulv.name=超低压二极管(§8ULV§r)
 gregtech.machine.diode.lv.name=低压二极管(§7LV§r)
@@ -4302,7 +4321,7 @@ gregtech.machine.quantum_tank.uv.name=量子缸 IV
 gregtech.machine.quantum_tank.uhv.name=量子缸 V
 
 #Buffers
-gregtech.machine.buffer.tooltip=能够存储物品与流体的小型缓存器
+gregtech.machine.buffer.tooltip=用于存储物品和流体的小小缓冲器
 gregtech.machine.buffer.lv.name=基础缓存器
 gregtech.machine.buffer.mv.name=进阶缓存器
 gregtech.machine.buffer.hv.name=进阶缓存器 II
@@ -4320,31 +4339,31 @@ tile.hermetic_casing.hermetic_casing_uhv.name=密封机械方块 IX
 
 #Gas Collectors
 gregtech.machine.gas_collector.lv.name=基础集气室
-gregtech.machine.gas_collector.lv.tooltip=收集不同维度的空气中种类各异的气体
+gregtech.machine.gas_collector.lv.tooltip=依照维度从空气中收集种类各异的气体
 gregtech.machine.gas_collector.mv.name=进阶集气室
-gregtech.machine.gas_collector.mv.tooltip=收集不同维度的空气中种类各异的气体
+gregtech.machine.gas_collector.mv.tooltip=依照维度从空气中收集种类各异的气体
 gregtech.machine.gas_collector.hv.name=进阶集气室 II
-gregtech.machine.gas_collector.hv.tooltip=收集不同维度的空气中种类各异的气体
+gregtech.machine.gas_collector.hv.tooltip=依照维度从空气中收集种类各异的气体
 gregtech.machine.gas_collector.ev.name=进阶集气室 III
-gregtech.machine.gas_collector.ev.tooltip=收集不同维度的空气中种类各异的气体
+gregtech.machine.gas_collector.ev.tooltip=依照维度从空气中收集种类各异的气体
 gregtech.machine.gas_collector.iv.name=精英集气室
-gregtech.machine.gas_collector.iv.tooltip=收集不同维度的大气中种类各异的气体
+gregtech.machine.gas_collector.iv.tooltip=依照维度从大气层中收集种类各异的气体
 gregtech.machine.gas_collector.luv.name=精英集气室 II
-gregtech.machine.gas_collector.luv.tooltip=收集不同维度的大气中种类各异的气体
+gregtech.machine.gas_collector.luv.tooltip=依照维度从大气层中收集种类各异的气体
 gregtech.machine.gas_collector.zpm.name=精英集气室 III
-gregtech.machine.gas_collector.zpm.tooltip=收集不同维度的大气中种类各异的气体
+gregtech.machine.gas_collector.zpm.tooltip=依照维度从大气层中收集种类各异的气体
 gregtech.machine.gas_collector.uv.name=终极集气室
-gregtech.machine.gas_collector.uv.tooltip=从太阳系中依照不同的维度收集种类各异的气体
+gregtech.machine.gas_collector.uv.tooltip=依照维度从太阳系中收集种类各异的气体
 gregtech.machine.gas_collector.uhv.name=史诗集气室
-gregtech.machine.gas_collector.uhv.tooltip=从太阳系中依照不同的维度收集种类各异的气体
+gregtech.machine.gas_collector.uhv.tooltip=依照维度从太阳系中收集种类各异的气体
 gregtech.machine.gas_collector.uev.name=史诗集气室 II
-gregtech.machine.gas_collector.uev.tooltip=从太阳系中依照不同的维度收集种类各异的气体
+gregtech.machine.gas_collector.uev.tooltip=依照维度从太阳系中收集种类各异的气体
 gregtech.machine.gas_collector.uiv.name=史诗集气室 III
-gregtech.machine.gas_collector.uiv.tooltip=从太阳系中依照不同的维度收集种类各异的气体
+gregtech.machine.gas_collector.uiv.tooltip=依照维度从太阳系中收集种类各异的气体
 gregtech.machine.gas_collector.uxv.name=史诗集气室 IV
-gregtech.machine.gas_collector.uxv.tooltip=从太阳系中依照不同的维度收集种类各异的气体
+gregtech.machine.gas_collector.uxv.tooltip=依照维度从太阳系中收集种类各异的气体
 gregtech.machine.gas_collector.opv.name=传说集气室
-gregtech.machine.gas_collector.opv.tooltip=从宇宙中依照不同的维度收集种类各异的气体
+gregtech.machine.gas_collector.opv.tooltip=依照维度从宇宙中收集种类各异的气体
 
 #Rock Breakers
 gregtech.machine.rock_breaker.lv.name=基础碎岩机
@@ -4362,17 +4381,17 @@ gregtech.machine.rock_breaker.luv.tooltip=岩浆冷却固化器 R-9200
 gregtech.machine.rock_breaker.zpm.name=精英碎岩机 III
 gregtech.machine.rock_breaker.zpm.tooltip=岩浆冷却固化器 R-10200
 gregtech.machine.rock_breaker.uv.name=终极碎岩机
-gregtech.machine.rock_breaker.uv.tooltip=火山成型仓
+gregtech.machine.rock_breaker.uv.tooltip=火山成型室
 gregtech.machine.rock_breaker.uhv.name=史诗碎岩机
-gregtech.machine.rock_breaker.uhv.tooltip=火山成型仓
+gregtech.machine.rock_breaker.uhv.tooltip=火山成型室
 gregtech.machine.rock_breaker.uev.name=史诗碎岩机 II
-gregtech.machine.rock_breaker.uev.tooltip=火山成型仓
+gregtech.machine.rock_breaker.uev.tooltip=火山成型室
 gregtech.machine.rock_breaker.uiv.name=史诗碎岩机 III
-gregtech.machine.rock_breaker.uiv.tooltip=火山成型仓
+gregtech.machine.rock_breaker.uiv.tooltip=火山成型室
 gregtech.machine.rock_breaker.uxv.name=史诗碎岩机 IV
-gregtech.machine.rock_breaker.uxv.tooltip=火山成型仓
+gregtech.machine.rock_breaker.uxv.tooltip=火山成型室
 gregtech.machine.rock_breaker.opv.name=传奇碎岩机
-gregtech.machine.rock_breaker.opv.tooltip=火山成型仓
+gregtech.machine.rock_breaker.opv.tooltip=火山成型室
 
 #Fisher
 gregtech.machine.fisher.lv.name=基础捕鱼机
@@ -4709,7 +4728,7 @@ gregtech.machine.large_chemical_reactor.name=大型化学反应釜
 gregtech.machine.large_combustion_engine.name=大型内燃引擎
 gregtech.machine.extreme_combustion_engine.name=极限内燃引擎
 gregtech.machine.large_combustion_engine.tooltip.boost_regular=提供§f20 L/s§7的氧气,并消耗§f双倍§7燃料以产生高达§f%s§7 EU/t的功率。
-gregtech.machine.large_combustion_engine.tooltip.boost_extreme=提供§f80 L/s§7的液氧,并消耗§f双倍§7燃料以产生高达§f%s§7 EU/t的功率。
+gregtech.machine.large_combustion_engine.tooltip.boost_extreme=提供§f80L/s§7的液态氧,并消耗§f双倍§7燃料以产生高达§f%s§7EU/t的功率。
 
 gregtech.machine.large_turbine.steam.name=大型蒸汽涡轮
 gregtech.machine.large_turbine.gas.name=大型燃气涡轮
@@ -4740,7 +4759,7 @@ gregtech.machine.large_miner.ev.name=基础采矿场
 gregtech.machine.large_miner.iv.name=进阶采矿场
 gregtech.machine.large_miner.luv.name=进阶采矿场 II
 gregtech.machine.miner.multi.modes=具有精准采集模式与区块对齐模式。
-gregtech.machine.miner.multi.production=粉碎矿石的产出量为§f研磨机§7的§f3倍§7。
+gregtech.machine.miner.multi.production=产出§f研磨机§7§f3x§7倍的粉碎矿石。
 gregtech.machine.miner.fluid_usage=每tick消耗§f%,d L§7的§f%s§7,超频时翻倍。
 gregtech.machine.miner.multi.description=一台工作范围极广的多方块采矿机,能够提供巨量矿石。
 gregtech.machine.miner.multi.needsfluid=钻井液不足!
@@ -4804,7 +4823,7 @@ gregtech.machine.power_substation.tooltip3=最多容许§f%d层电容§7。
 gregtech.machine.power_substation.tooltip4=每§f24小时§7损失相当于总容量的§f1%%§7的能量。
 gregtech.machine.power_substation.tooltip5=每个电容的损失上限为§f%,d EU/t§7。
 gregtech.machine.power_substation.tooltip6=可以使用
-gregtech.machine.power_substation.tooltip6.5= 激光仓§7。
+gregtech.machine.power_substation.tooltip6.5=激光仓§7。
 gregtech.multiblock.power_substation.description=蓄能变电站是一个用于储存大量能量的多方块结构,它最多可以容纳18层电容,每层都必须填充完整,这意味着你可以用空电容来填充空间。
 
 gregtech.machine.active_transformer.name=有源变压器
@@ -4933,7 +4952,6 @@ gregtech.machine.item_bus.export.uhv.name=§4UHV§r输出总线
 
 gregtech.bus.collapse_true=已启用物品堆叠自动合并
 gregtech.bus.collapse_false=已禁用物品堆叠自动合并
-gregtech.bus.collapse.error=总线位于已成型的多方块结构后方可进行该操作
 
 gregtech.machine.fluid_hatch.import.tooltip=为多方块结构输入流体
 
@@ -5239,15 +5257,27 @@ gregtech.machine.laser_hatch.tooltip2=§c激光传导线缆必须直线摆放!
 gregtech.machine.fluid_tank.max_multiblock=多方块结构最大尺寸:%,dx%,dx%,d
 gregtech.machine.fluid_tank.fluid=含有%s mB%s
 
-gregtech.machine.me_export_fluid_hatch.name=ME输出仓
+# ME Parts
+gregtech.machine.me_import_item_bus.name=ME输入总线
+gregtech.machine.me.item_import.tooltip=从ME网络提取指定物品
+gregtech.machine.me_stocking_item_bus.name=ME库存输入总线
+gregtech.machine.me.stocking_item.tooltip=直接从ME网络抽取物品
+gregtech.machine.me.stocking_item.tooltip.2=ME自动拉取模式将自动标记ME网络中的前16种物品,每5秒更新一次。
+gregtech.machine.me_import_item_hatch.configs.tooltip=可标记16种物品
+gregtech.machine.me_import_fluid_hatch.name=ME输入仓
+gregtech.machine.me.fluid_import.tooltip=从ME网络提取指定流体
+gregtech.machine.me_stocking_fluid_hatch.name=ME库存输入仓
+gregtech.machine.me.stocking_fluid.tooltip=直接从ME网络抽取流体
+gregtech.machine.me.stocking_fluid.tooltip.2=ME自动拉取模式将自动标记ME网络中的前16种流体,每5秒更新一次。
+gregtech.machine.me_import_fluid_hatch.configs.tooltip=可标记16种流体
 gregtech.machine.me_export_item_bus.name=ME输出总线
-gregtech.machine.me_import_fluid_hatch.name=ME库存输入仓
-gregtech.machine.me_import_item_bus.name=ME库存输入总线
-gregtech.machine.me.fluid_export.tooltip=将流体直接存储到ME网络中。
-gregtech.machine.me.item_export.tooltip=将物品直接存储到ME网络中。
-gregtech.machine.me.fluid_import.tooltip=自动从ME网络获取流体。
-gregtech.machine.me.item_import.tooltip=自动从ME网络获取物品。
-gregtech.machine.me.export.tooltip=在连接到ME网络之前,它具有无限容量。
+gregtech.machine.me.item_export.tooltip=将物品直接存储到ME网络中
+gregtech.machine.me.item_export.tooltip.2=可以缓存无限数量的物品
+gregtech.machine.me_export_fluid_hatch.name=ME输出仓
+gregtech.machine.me.fluid_export.tooltip=将流体直接存储到ME网络中
+gregtech.machine.me.fluid_export.tooltip.2=可以缓存无限数量的流体
+gregtech.machine.me.stocking_auto_pull_enabled=ME自动拉取已启用
+gregtech.machine.me.stocking_auto_pull_disabled=ME自动拉取已禁用
 
 # Universal tooltips
 gregtech.universal.tooltip.voltage_in=§a输入电压:§f%,d EU/t(%s§f)
@@ -5270,7 +5300,7 @@ gregtech.universal.tooltip.item_stored=§d内含物品:§f%2$,d + %3$,d 个%1$
 gregtech.universal.tooltip.item_transfer_rate=§b传输速率:§f%,d件物品/s
 gregtech.universal.tooltip.item_transfer_rate_stacks=§b传输速率:§f%,d组物品/s
 gregtech.universal.tooltip.fluid_storage_capacity=§9流体容量:§f%,d L
-gregtech.universal.tooltip.fluid_storage_capacity_mult=§9流体容量:共§f%d§7个流体槽,每个§f%dL§7
+gregtech.universal.tooltip.fluid_storage_capacity_mult=§9流体容量:§7共§f%d§7个流体槽,每个§f%dL§7
 gregtech.universal.tooltip.fluid_stored=§d内含流体:§f%2$,d L %1$s
 gregtech.universal.tooltip.fluid_transfer_rate=§b传输速率:§f%,d L/t
 gregtech.universal.tooltip.parallel=§d最大并行:§f%d
@@ -5366,9 +5396,12 @@ gregtech.gui.fluid_auto_output.tooltip.enabled=流体自动输出已启用
 gregtech.gui.fluid_auto_output.tooltip.disabled=流体自动输出已禁用
 gregtech.gui.item_auto_output.tooltip.enabled=物品自动输出已启用
 gregtech.gui.item_auto_output.tooltip.disabled=物品自动输出已禁用
+gregtech.gui.item_auto_collapse.tooltip.enabled=物品堆叠自动合并已启用
+gregtech.gui.item_auto_collapse.tooltip.disabled=物品堆叠自动合并已禁用
 gregtech.gui.charger_slot.tooltip=§f充电槽§r/n§7从%s电池中取电§7/n§7也可为%s工具或电池充电
-gregtech.gui.configurator_slot.tooltip=§f编程电路槽§r/n§a配置值:§f%d§7/n/n§7左击/右击/滚轮可浏览循环列表/n§7Shift+右键单击以清除
+gregtech.gui.configurator_slot.tooltip=§f编程电路槽§r\n§a配置值:§f%d§7\n\n§7左击/右击/滚轮可循环浏览列表\n§7Shift+左键单击以打开选择GUI\n§7Shift+右键单击以清除
 gregtech.gui.configurator_slot.no_value=空
+gregtech.gui.configurator_slot.unavailable.tooltip=编程电路槽位无法使用
 gregtech.gui.fluid_lock.tooltip.enabled=流体锁定已启用
 gregtech.gui.fluid_lock.tooltip.disabled=流体锁定已禁用
 gregtech.gui.fluid_voiding.tooltip.enabled=过量流体销毁已启用
@@ -5385,8 +5418,12 @@ gregtech.gui.me_network.offline=网络状态:§4离线§r
 gregtech.gui.waiting_list=发送队列:
 gregtech.gui.config_slot=§f配置槽位§r
 gregtech.gui.config_slot.set=§7点击§b设置/选择§7配置槽位。§r
+gregtech.gui.config_slot.set_only=§7点击§b设置§7配置槽位。§r
 gregtech.gui.config_slot.scroll=§7使用滚轮§a切换§7配置数。§r
 gregtech.gui.config_slot.remove=§7右击§4清除§7配置槽位。§r
+gregtech.gui.config_slot.auto_pull_managed=§4禁用:§7由ME自动拉取管理
+gregtech.gui.me_bus.extra_slot=额外槽位/n§7在这里放置配方的额外物品,例如模具或透镜
+gregtech.gui.me_bus.auto_pull_button=单击以切换ME自动拉取模式
 gregtech.gui.alarm.radius=半径:
 
 
@@ -5555,7 +5592,7 @@ gregtech.multiblock.pattern.error.batteries=§c必须至少有一个不是空电
 gregtech.multiblock.pattern.error.filters=§c必须使用同种过滤器§r
 gregtech.multiblock.pattern.clear_amount_1=§6前方必须有1x1x1大小的空间§r
 gregtech.multiblock.pattern.clear_amount_3=§6前方必须有3x3x1大小的空间§r
-gregtech.multiblock.pattern.single=§6仅可使用该种方块§r
+gregtech.multiblock.pattern.single=§6仅可使用该种方块类型§r
 gregtech.multiblock.pattern.location_end=§c最末端§r
 gregtech.multiblock.pattern.replaceable_air=可为空气
 
@@ -5640,8 +5677,10 @@ gregtech.multiblock.computation.not_enough_computation=机器需要更多算力
 gregtech.multiblock.power_substation.stored=存储:%s
 gregtech.multiblock.power_substation.capacity=容量:%s
 gregtech.multiblock.power_substation.passive_drain=被动损失:%s
-gregtech.multiblock.power_substation.average_io=平均输入/输出:%s
-gregtech.multiblock.power_substation.average_io_hover=蓄能变电站内部的平均能量变化
+gregtech.multiblock.power_substation.average_in=平均EU输入:%s
+gregtech.multiblock.power_substation.average_out=平均EU输出:%s
+gregtech.multiblock.power_substation.average_in_hover=蓄能变电站内部的平均功率输入
+gregtech.multiblock.power_substation.average_out_hover=蓄能变电站内部的平均功率输出
 gregtech.multiblock.power_substation.time_to_fill=预计充满时间:%s
 gregtech.multiblock.power_substation.time_to_drain=预计耗空时间:%s
 gregtech.multiblock.power_substation.time_seconds=%s秒
diff --git a/src/main/resources/assets/gregtech/models/item/material_sets/dull/tool_head_wirecutter.json b/src/main/resources/assets/gregtech/models/item/material_sets/dull/tool_head_wirecutter.json
new file mode 100644
index 00000000000..92690c87e45
--- /dev/null
+++ b/src/main/resources/assets/gregtech/models/item/material_sets/dull/tool_head_wirecutter.json
@@ -0,0 +1,6 @@
+{
+  "parent": "item/generated",
+  "textures": {
+    "layer0": "gregtech:items/material_sets/dull/tool_head_wire_cutter"
+  }
+}
diff --git a/src/main/resources/assets/gregtech/models/item/tools/wire_cutter_hv.json b/src/main/resources/assets/gregtech/models/item/tools/wire_cutter_hv.json
new file mode 100644
index 00000000000..60a5a9255c1
--- /dev/null
+++ b/src/main/resources/assets/gregtech/models/item/tools/wire_cutter_hv.json
@@ -0,0 +1,7 @@
+{
+  "parent": "item/handheld",
+  "textures": {
+    "layer0": "gregtech:items/tools/handle_electric_wire_cutter_hv",
+    "layer1": "gregtech:items/tools/wire_cutter_electric"
+  }
+}
diff --git a/src/main/resources/assets/gregtech/models/item/tools/wire_cutter_iv.json b/src/main/resources/assets/gregtech/models/item/tools/wire_cutter_iv.json
new file mode 100644
index 00000000000..6a7f378d8a9
--- /dev/null
+++ b/src/main/resources/assets/gregtech/models/item/tools/wire_cutter_iv.json
@@ -0,0 +1,7 @@
+{
+  "parent": "item/handheld",
+  "textures": {
+    "layer0": "gregtech:items/tools/handle_electric_wire_cutter_iv",
+    "layer1": "gregtech:items/tools/wire_cutter_electric"
+  }
+}
diff --git a/src/main/resources/assets/gregtech/models/item/tools/wire_cutter_lv.json b/src/main/resources/assets/gregtech/models/item/tools/wire_cutter_lv.json
new file mode 100644
index 00000000000..406df61785c
--- /dev/null
+++ b/src/main/resources/assets/gregtech/models/item/tools/wire_cutter_lv.json
@@ -0,0 +1,7 @@
+{
+  "parent": "item/handheld",
+  "textures": {
+    "layer0": "gregtech:items/tools/handle_electric_wire_cutter_lv",
+    "layer1": "gregtech:items/tools/wire_cutter_electric"
+  }
+}
diff --git a/src/main/resources/assets/gregtech/textures/blocks/misc/itnt.png b/src/main/resources/assets/gregtech/textures/blocks/misc/itnt.png
new file mode 100644
index 00000000000..a167e1676b7
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/blocks/misc/itnt.png differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/misc/powderbarrel.png b/src/main/resources/assets/gregtech/textures/blocks/misc/powderbarrel.png
new file mode 100644
index 00000000000..b5624295e93
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/blocks/misc/powderbarrel.png differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_bus.png b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_bus.png
index 76486f57cdd..dd3d1f12037 100644
Binary files a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_bus.png and b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_bus.png differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_bus_active.png b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_bus_active.png
new file mode 100644
index 00000000000..c9bb59b3b46
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_bus_active.png differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_hatch.png b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_hatch.png
index f19def26e30..a2644c44b46 100644
Binary files a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_hatch.png and b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_hatch.png differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_hatch_active.png b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_hatch_active.png
new file mode 100644
index 00000000000..fd4570e0f9f
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_input_hatch_active.png differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_bus.png b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_bus.png
index 96ac2920c71..8914379ca05 100644
Binary files a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_bus.png and b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_bus.png differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_bus_active.png b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_bus_active.png
new file mode 100644
index 00000000000..39de0bb51bf
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_bus_active.png differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_hatch.png b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_hatch.png
index 313e4fdbeb6..e13a7f0fb7c 100644
Binary files a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_hatch.png and b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_hatch.png differ
diff --git a/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_hatch_active.png b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_hatch_active.png
new file mode 100644
index 00000000000..de4b2100518
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/blocks/overlay/appeng/me_output_hatch_active.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/base/background_popup.png b/src/main/resources/assets/gregtech/textures/gui/base/background_popup.png
new file mode 100644
index 00000000000..f77849e8f7b
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/base/background_popup.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/base/slot_dark.png b/src/main/resources/assets/gregtech/textures/gui/base/slot_dark.png
new file mode 100644
index 00000000000..90a91082dc3
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/base/slot_dark.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/arrow_double.png b/src/main/resources/assets/gregtech/textures/gui/widget/arrow_double.png
new file mode 100644
index 00000000000..5613c4a3785
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/arrow_double.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/button_cross.png b/src/main/resources/assets/gregtech/textures/gui/widget/button_cross.png
new file mode 100644
index 00000000000..863553adbdc
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/button_cross.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/button_me_auto_pull.png b/src/main/resources/assets/gregtech/textures/gui/widget/button_me_auto_pull.png
new file mode 100644
index 00000000000..16ada2daad4
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/button_me_auto_pull.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/button_redstone_off.png b/src/main/resources/assets/gregtech/textures/gui/widget/button_redstone_off.png
new file mode 100644
index 00000000000..e51d4534755
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/button_redstone_off.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/button_redstone_on.png b/src/main/resources/assets/gregtech/textures/gui/widget/button_redstone_on.png
new file mode 100644
index 00000000000..44d6e446b72
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/button_redstone_on.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_case_sensitive.png b/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_case_sensitive.png
new file mode 100644
index 00000000000..6615e156d4f
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_case_sensitive.png differ
diff --git a/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_match_all.png b/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_match_all.png
new file mode 100644
index 00000000000..b86b782c8de
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/gui/widget/ore_filter/button_match_all.png differ
diff --git a/src/main/resources/assets/gregtech/textures/items/tools/handle_electric_wire_cutter_hv.png b/src/main/resources/assets/gregtech/textures/items/tools/handle_electric_wire_cutter_hv.png
new file mode 100644
index 00000000000..b54d8d136c2
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/items/tools/handle_electric_wire_cutter_hv.png differ
diff --git a/src/main/resources/assets/gregtech/textures/items/tools/handle_electric_wire_cutter_iv.png b/src/main/resources/assets/gregtech/textures/items/tools/handle_electric_wire_cutter_iv.png
new file mode 100644
index 00000000000..303fea217f6
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/items/tools/handle_electric_wire_cutter_iv.png differ
diff --git a/src/main/resources/assets/gregtech/textures/items/tools/handle_electric_wire_cutter_lv.png b/src/main/resources/assets/gregtech/textures/items/tools/handle_electric_wire_cutter_lv.png
new file mode 100644
index 00000000000..c45bffa4360
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/items/tools/handle_electric_wire_cutter_lv.png differ
diff --git a/src/main/resources/assets/gregtech/textures/items/tools/wire_cutter_electric.png b/src/main/resources/assets/gregtech/textures/items/tools/wire_cutter_electric.png
new file mode 100644
index 00000000000..602eea83d49
Binary files /dev/null and b/src/main/resources/assets/gregtech/textures/items/tools/wire_cutter_electric.png differ
diff --git a/src/main/resources/gregtech_at.cfg b/src/main/resources/gregtech_at.cfg
index 343dd1b65f5..76892c9fc05 100644
--- a/src/main/resources/gregtech_at.cfg
+++ b/src/main/resources/gregtech_at.cfg
@@ -81,3 +81,6 @@ protected net.minecraft.entity.item.EntityBoat field_184473_aH # lastYd
 
 # EntityItem
 public net.minecraft.entity.item.EntityItem field_145804_b # pickupDelay
+
+# Explosion
+public net.minecraft.world.Explosion field_77283_e # exploder
diff --git a/src/test/java/gregtech/api/util/OreGlobTest.java b/src/test/java/gregtech/api/util/OreGlobTest.java
index f9f888f02f2..fb094484f73 100644
--- a/src/test/java/gregtech/api/util/OreGlobTest.java
+++ b/src/test/java/gregtech/api/util/OreGlobTest.java
@@ -190,11 +190,7 @@ public void matchTest() {
         assertMatch(expr, "anyDoubleSomething", false);
         assertMatch(expr, "shouldn't match!", false);
 
-        expr = compile("$c caseSensitiveMatch");
-        assertMatch(expr, "casesensitivematch", false);
-        assertMatch(expr, "caseSensitiveMatch", true);
-
-        expr = compile("$\\c caseSensitiveMatch");
+        expr = compile("caseSensitiveMatch", true);
         assertMatch(expr, "casesensitivematch", false);
         assertMatch(expr, "caseSensitiveMatch", true);
 
@@ -249,8 +245,6 @@ public void matchTest() {
     @Test
     public void errorTest() {
         assertReport("End of file after escape character ('\\'): \\", true);
-        assertReport("$asdf Tags at middle of expression $12345", true);
-        assertReport("End of file after escape character ('\\'): $123\\", true);
         assertReport(")", true);
         assertReport("a | b | c | ", true);
         assertReport(")))))))", true);
@@ -262,12 +256,15 @@ public void errorTest() {
 
         assertReport("dust !impure !iron", false);
         assertReport("dust !(impure) !(iron)", false);
-        assertReport("$cc 1", false);
     }
 
     private static OreGlob compile(String expression) {
+        return compile(expression, false);
+    }
+
+    private static OreGlob compile(String expression, boolean caseSensitive) {
         long t = System.nanoTime();
-        OreGlobCompileResult result = new OreGlobParser(expression).compile();
+        OreGlobCompileResult result = new OreGlobParser(expression, !caseSensitive).compile();
         assertThat(result.hasError(), is(false));
 
         if (LOG) {
@@ -333,7 +330,7 @@ protected boolean matchesSafely(OreGlob item) {
     }
 
     private static void assertReport(String expression, boolean error) {
-        OreGlobCompileResult result = new OreGlobParser(expression).compile();
+        OreGlobCompileResult result = new OreGlobParser(expression, true).compile();
         assertThat(result, new TypeSafeMatcher<>(OreGlobCompileResult.class) {
 
             @Override