From 02bedd5c0a96a654c6692edd1b7f2765fd9c46b4 Mon Sep 17 00:00:00 2001
From: BlueWeabo <ilia.iliev2005@gmail.com>
Date: Sat, 6 Jan 2024 00:41:00 +0200
Subject: Merge MuTEMaster into Master (#2431)

* Fix MuTE structure check and power intake (#1975)

* Fixed casing lists being cleared for every checked structure piece

* Fixed power being taken from any side BUT the right one

* ACP Structure - Rebased (#1978)

* Added 2nd structure tier to ACR and make complex parallels depend on it

* Added tier 3 to 8 structure pieces to ACR

* Added disclaimer

* Renamed ACR to ACP because MV CR already is named ACR

* Add autopush functionality to MuTE (#1976) - fix conflict

* Working auto push

* Revert wildcard import

* Addresssed reviews

* Fix reference issue

* Minor MuTE fixes - Rebased (#1983)

* Fixed ACP recipe map

* Fixed controller side being used instead of part side when accessing tanks

* Fix Structure not forming (#1984)

* fix cables not connecting

* fix structure and don't store controller

* Add missing tooltips (#1981)

* Add missing tooltips

* Address blue's change

* Distillation MuTE (#1989)

* Started work on DT MuTE

* Renamed methods so they also make sense when used horizontally

* MuTE Upgrade Casings - Rebased (#1988) - fix conflict

* Added cleanroom upgrade casing

* Added inventory and tank upgrades

* Added tooltips to mute casings

* Added power upgrades

* Set player UUID when placing MuTE

* MuTE fixes (#1991)

* Fixed pipes not connectable to MuTE casings

* Fixed not all things being renamed to ACP

* Fix running in obf

* fix for real yo

* Add a Generic Processing Logic and extract methods - Rebased (#1992)

* add a generic processing logic

* calculate tier in another method

* calculate power logic in another method

* Add Layered Coke Foundry (#1995)

* Add the Foundry class and call it

* Foundry name correction

* Buildable stackable structure

* Fixed min stacks and added motor casings

* checkMachine override for custom checking

* Working checkMachine for all stacks, and recipes

* Fix getOutputFluids

* Change recipe processing to GenericProcessingLogic

* Change inventoryName to protected for override

* Override checkRecipe for multis that consume EU

* Rename class and add inner walls to multi

* Structure update and other fixes

* Fix processing logic being static

* MuTE inventory upgrade logic (#2082)

* Catch potential NPE

* Don't load name when it doesn't exist

* Potentially cause weird non-replicatable issue where registry ends up with different key

* Use proper block removal method

* Validate index before using it

* Don't open controller GUI from inventory upgrade

* semi-working concept

* sync the ID of the inventory upgrade to correctly remove it later

* remove unneeded boolean

---------

Co-authored-by: BlueWeabo <76872108+BlueWeabo@users.noreply.github.com>

* fixed layeredCokeBattery checkMachine to prevent 'already in building state' (#2099)

* MuTE casing structure element (#2105) - fix conflict

* Added class containing MuTE relevant structure elements

* Migrate MuTE to new structure element

* Formatting fix

* Use int array instead of int hashmap, since its expected these arrays will never get long enough to be faster as hashmap

* Delete old code

* Cache MuTEs for non-instance specific actions (#2109)

* Introduce map of cached TEs, which are used to perform actions that don't require a specific instance of the TE. This prevents constant creation of new TEs

* Remove static modifier from map of cached TEs

* First Modular Upgrade Casings Implementation (#2142)

* Base support for Heater MUCs

- Define Heater upgrade casings;
- Create the 5 tiers of Heaters;
- Add method to increase and decrease count.

* Refactor the cache of MUCs in structure

- Change the way that each MUC is counted: since there will be several types, the integer that counted heaters is now a hashmap that divides all MUCs based on their type and tier, to be counted separately from each other.

* Add Insulator MUC

- Add second MUC type (insulator) to test alongside heaters on the Layered Coke Battery.

* Fix MUC count reset

* Refactor MUC implementation into subclass

- Move the new methods and hashmap away from the base classes, and onto a s specific one that won't be used by unrelated multiblocks.

* Remove empty lines

* Refactor MUC implementations into subclasses

* Requested fixes in StackableModularController

* Change hashmap keys to an enum

* Hashmap getter for load order purposes

- Added a getter that generates the default value for the hashmap if it is null, due to problems with load order;

* Apply spotless

* NotNull annotations

* More Additions to MUCs and the LCB (#2215)

* Fix old LCB multi name in some locations

* Refactor mucMap and override checkRecipe

- Refactor mucMap to an array of primitive integers instead of the wrapper type, for ease of use with other methods such as stream;
- Override checkRecipe for custom recipe behavior on MUC multis, to be implemented in a future commit;

* First implementation of bonuses and MUC requirements

- Change EU/t and recipe duration of this multi based on the count of different MUCs in the multi;
- Fail the structure check based on the count of each of the allowed MUCs.

* Parallel count implementation

- Calculate parallels based on the count of base MUCs, the cheapest option amongst the possibilities, in this case heaters;
- Added more abstract methods to require specific values from the multi classes.

* Fix parallel count and processing

- Fixed the handling of parallels by pointing to the corrent maxParallel variable in ProcessingLogic.

* Test of parallels with additional amp input

* Structure fix for the intended LCB

- Changed MUC placements to match what I intended at the beginning, to better test the multi.

* One more comment

* Remove checkRecipe override

* Refactor Item and Fluid to be in separate logic classes (#2178) - fix
conflict

* basics of inventory logic

* mostly working item logic

* working nbt saving/loading

* fluid handler

* FluidSlotHandler WIP

* fluid handler mostly working

* remove fluid handler from gt5u

* prepare for conversion

* use correct imports

* spotless

* more controller logic

* spotless

* final refactor. migration next

* spotless

* add more methods to logic classes

* convert almost everything to use new Logic

* spotless

* make mute casing mode an int

* allow pump cover to work with FluidInventoryLogic

* pumps work

* spotless

* make item inventory logic work with every item input thing

* rework Fluid Inventory Logic to work with all fluid inputs

* spotless

* address annotation reviews

* finish off todos

* missed to dos

* cleanup

Coke oven will get a new GUI when i get to it

* address review

* prevent npes from ControllerXXXLogic

* null checks

* remove accidentally added methods

* fix missed return

* fixes after rebase - fix conflict

* Laser Engraver Multi. (#2223) - fix conflict

* saving.............

* clean up

* savin

* Small fixes + Adding back stuff, Crashes you and spams logs.

* fix stack overflow

* Fixes

* Fixes

---------

Co-authored-by: BlueWeabo <76872108+BlueWeabo@users.noreply.github.com>

* Add TickableTask (#2216)

* Add autopush functionality to MuTE (#1976)

* Working auto push

* Revert wildcard import

* Addresssed reviews

* Fix reference issue

* MuTE Upgrade Casings (#1988)

* Added cleanroom upgrade casing

* Added inventory and tank upgrades

* Added tooltips to mute casings

* Added power upgrades

* Set player UUID when placing MuTE

* Add a Generic Processing Logic and extract methods (#1992)

* add a generic processing logic

* calculate tier in another method

* calculate power logic in another method

* MuTE inventory upgrade logic (#2082)

* Catch potential NPE

* Don't load name when it doesn't exist

* Potentially cause weird non-replicatable issue where registry ends up with different key

* Use proper block removal method

* Validate index before using it

* Don't open controller GUI from inventory upgrade

* semi-working concept

* sync the ID of the inventory upgrade to correctly remove it later

* remove unneeded boolean

---------

Co-authored-by: BlueWeabo <76872108+BlueWeabo@users.noreply.github.com>

* MuTE casing structure element (#2105)

* Added class containing MuTE relevant structure elements

* Migrate MuTE to new structure element

* Formatting fix

* Use int array instead of int hashmap, since its expected these arrays will never get long enough to be faster as hashmap

* Delete old code

* Refactor Item and Fluid to be in separate logic classes (#2178)

* basics of inventory logic

* mostly working item logic

* working nbt saving/loading

* fluid handler

* FluidSlotHandler WIP

* fluid handler mostly working

* remove fluid handler from gt5u

* prepare for conversion

* use correct imports

* spotless

* more controller logic

* spotless

* final refactor. migration next

* spotless

* add more methods to logic classes

* convert almost everything to use new Logic

* spotless

* make mute casing mode an int

* allow pump cover to work with FluidInventoryLogic

* pumps work

* spotless

* make item inventory logic work with every item input thing

* rework Fluid Inventory Logic to work with all fluid inputs

* spotless

* address annotation reviews

* finish off todos

* missed to dos

* cleanup

Coke oven will get a new GUI when i get to it

* address review

* prevent npes from ControllerXXXLogic

* null checks

* Base work

* PollutionTask

* move package

* Fix generics

* Internal -> OverrideOnly

* rebase fix

* Ducttape addPollution

---------

Co-authored-by: Maxim <maxim235@gmx.de>
Co-authored-by: BlueWeabo <ilia.iliev2005@gmail.com>
Co-authored-by: BlueWeabo <76872108+BlueWeabo@users.noreply.github.com>

* Rework a bit of ProcessingLogic to fit MuTEs (#2283) - fix conflicts

* Add a way to enable or disable the crafting buffer on GPL multiblocks (#2218)

* add a way to enable or disable the crafting buffer on GPL multiblocks

* don't register the hatch either

* fix Refractory Capsule (#2219)

* Fix PAs overclocking ulv recipes too much (#2220)

* fix PAs overclocking ulv recipes too much

* make sure we save the returned value

* Fix Digital Tank capacity for Fluid Storage Monitor (#2217)

* Fix Digital Tank capacity for Fluid Storage Monitor

* Annotations

* Blacklist AE2FC drop and packet, and Chisel stones from Recycler (#2222)

* Fix recycler blacklist being sensitive to NBT

* Blacklist AE2FC drop and packet, and Chisel stones

* fix class loader issue

* Add detailed logging for ME hatches (#2224)

* Fix overclock calculator calculating eu/t use for ulv recipe wrong on certain parallel (#2225)

* fix overclock calculator calculating eu/t use for ulv recipe wrong on certain parallel

* make formula into its own method

* Fix drilling rigs, plants and concrete backfiller to fail with multiple energy hatches (#2227)

* max-1-energy-hatch-in-drilling-rigs.-plants-and-concrete-backfiller

* spotlessApply (#2228)

Co-authored-by: GitHub GTNH Actions <>

* revert

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* clear stale crafting input bus list (#2233)

* add ability to remove item data and use it for clay (#2229)

* Update buildscript (#2232)

* Fix server crash with RecipeFilter (#2231)

* Fix server crash with RecipeFilter

* Make client send filtered machines to server

* Use mUniqueIdentifier

* Fix a NPE w/ injecting into super/quantum chests (#2234)

When simulating an injection, if the stackSize > chest's capacity, it
causes a NPE when the internal chest is empty.

Also fixes a potential bug when void overflow is set; the chest should
return null in such a scenario regardless of simulation/modulation.

* Fix lag caused by getRecipeMap for PA (#2236)

* Experimental fix to prevent infinite loop in Grid destruction (#2235)

Co-authored-by: Firenoo <49818773+firenoo@users.noreply.github.com>

* Fix ME Output Bus and Crafting Input Bus overflow when save/load (#2238)

* Remove fire display from singleblock generator (#2240)

* Fix some output slots allowing insertion (#2230)

* fix overlay zfighting by disable depth test (#2226)

* Crafting input - Optimize isEmpty check to reduce lag (#2239)

* Optimize isEmpty check

* rearrage

* remove broken hsla recipe (#2241)

* Correct PCB Factory Energy Hatch description (#2237)

* Correct PCB Factory Energy Hatch description

Changes the PCB factory description (the one seen when holding shift) which currently says "Energy Hatches: 1+"

I believe this is incorrect and that the correct description is 1-2 energy hatches or 1 TT energy hatch.

I believe the PCB factory uses this, which checks for 1-2 or 1 TT:

public boolean checkExoticAndNormalEnergyHatches() {
        if (mExoticEnergyHatches.isEmpty() && mEnergyHatches.isEmpty()) {
            return false;
        }

        if (!mExoticEnergyHatches.isEmpty()) {
            if (!mEnergyHatches.isEmpty()) {
                return false;
            }

            if (mExoticEnergyHatches.size() != 1) {
                return false;
            }
        }

        return mEnergyHatches.size() <= 2;
    }

* gradlew spotlessApply

* Correct file name on resource pack guide (#2242)

* Fix GT_RecipeConstants.Fuel (#2243)

* Update text (#2246)

* Fix startup tier for fusion NEI (#2249)

* Update the conditionals buttons and tooltips on covers to reflect their actual effects (#2244)

* Update redstone buttons and tooltips to better reflect actual use

* Spotless Apply

* Update GT_Cover_FluidRegulator.java

* Update GT_Cover_FluidRegulator.java

* Typo fix, Icon Improved and interactive blocking ui

- Fixed a typo in the world machine
- Fixed double button situation for conveyor belts.
  - There was never any issue, the testing methodology gave me invalid results.
  - Conveyor behaviour is in line with all the other covers affected by this PR/Branch.
- Updated icon for the machine state to be a miniature machine controller cover.
- Made the block/allow input section more interactive in order to better reflect the actual effect of these buttons.
  - In import mode, it actually blocks the machine from outputting from that side.

* typos

I can't write to save myself sometimes

* Better text alignment

- Better text alignment

* fix typos

I swear I can't write to save myself.

---------

Co-authored-by: Martin Robertz <dream-master@gmx.net>

* Fix Orichalcum and Shadowiron smelting (#2251)

* fix orichalcum and shadowiron smelting

* add Alduorite and Chrysotile

* Fix tier display for Fusion NEI header (#2250)

* fix ulv recipes being broken again when under 1 tick calculation is taken (#2254)

* change way to fix zfighting (#2253)

* Crafting input hatches QoLs (#2200)

* Fixes + Detect Inventory Slot Changes

* support rename + check for updates

* add back onChangeListener + fix npe

* ICustomNameObject TileEntity

* Fix NPEs

* Use IInterfaceTerminalSupport

* fix

* register

* dep

* spotless

* General Crafting Input Hatch QoL fixes (#2212)

* feat: refactor naming && include circuit and catalyst in default name

* feat: add 4 more slot to solve my ocd

* fix: formatting

* feat: migrate from 4x8 to 4x9

* spotlessApply (#2213)

Co-authored-by: GitHub GTNH Actions <>

* QoLs

* 9 manual items

* spotless

* feat: open master GUI when used, without holding a data-stick (#2221)

* fix destpos

* optimize empty check

* Fix error when fluidInventory.size() == 0

If the fluidInventory size is 0, there is no element to get. Add a check
for it.

* name in waila + fix int overflow

* unnecssary super

* update deps

---------

Co-authored-by: Fox_white <39846845+foxwhite25@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Firenoo <49818773+firenoo@users.noreply.github.com>
Co-authored-by: Martin Robertz <dream-master@gmx.net>

* Use real stack limit (#2256)

* Fix incorrect data stick behaviors for hatches (#2257)

* Fix pcb factory not applying its roughness multiplier when it doesn't OC (#2258)

* fix PCB Factory not applying its roughness multiplier when it doesn't do any overclocks

* spotless

* Remove duplicate obsidian long rod (#2259)

* disable gt obsidian long rod

* cleaner code

* Add hazmat to ThaumicBoots (#2260)

* Add hazmat to ThaumicBoots

does what it says

* fixed

* fix item names (#2263)

* Fix renaming recipe check might ignore NBT equality (#2261)

* Fix GPL ignoring if the recipe is allowed to be cached (#2262)

* Added bricked blast furnace recipe progress to waila. (#2265)

* Added bricked blast furnace recipe progress to waila.

* fixed formatting issues.

* Fix cutter recipes not being added (#2271)

* add processing task

* clean up item logic host

* temporary fix for GT_StructrureMuTE

* use j9+ feature on pollution task

* prepare complex parallel logic for transition

* feature to ProcessingLogicHost

* fix up multiblock bases

* add processing logic for each multi to prepare for transition

* spotless

* removed debug text from wailaBody of GT_MetaTileEntity_Hatch_CraftingInput_ME (#2272)

* Proper recipe selection for output overflow in LCR and other multiblocks (#2247)

* Implement Stream<FindRecipeResult> findRecipesWithResult for GT_RecipeMap

* Change ProcessingLogic.process to actually use new  findRecipesWithResult

* Change ProcessingLogic.process to start finding something only for OUTPUT_FULL result

* Refactor ProcessingLogic.process to make logic more readable

* Replace while with for loop, remove NOT_FOUND return in end of findRecipesWithResult

* Apply spotless

* Make findRecipe use findRecipes, add annotation to GT_Recipe and FindRecipeResult for processRecipe and make method protected, replace wildcard imports

* Remake isRecipeWithOutputFullFound

* Add @Nonnull to methods

* Apply spotless

* Remove Stream version of findRecipeWithResult, replace with predicate one. Add GT_Predicated_Recipe_Map class for utilizing this method. Changes some existent recipe maps to inherit from base class.

* Remove GT_Predicated_Recipe_Map, add Predicate directly to GT_Recipe_Map#findRecipeWithResult. Add AdvancedRecipeValidatorPredicate and FindRecipeWithAdvancedValidatorResult to allow store validation calculations for further use and proper errors displaying.

* Fix InsufficientVoltage errors

* Changes according to review comments. Integrate FindRecipeWithAdvancedValidatorResult to FindRecipeResult, rename AdvancedRecipeValidatorPredicate, encapsulate AdvancedRecipeValidatorPredicate fields, fixes some typos, etc

* Moves InsufficientVoltage check to GT_ParallelHelper. Removes FindRecipeResult#State#INSUFFICIENT_VOLTAGE

* Return an old findRecipeWithResult

* Renames things, call old methods for singleblocks

* Renames things, makes FindRecipeResult ctor private

* Apply spotless

* Move RecipeValidator, fix comments typos

* update deps

* fix up complex processing logic

* add a getter for voiding mode

* fix getAccessibleSlotsFromSide being wrong sometimes

* allow for subtraction of a specific item

* use long for amount

* add a setter for machine host

* initial work on finding recipes and input consumption

* Deprecate PA by removing its controller recipe (#2273)

* Restore PA controller recipe (#2276)

* Restore PA controller recipe

* Remove duplicated recipe

* Add optional description to input hatch constructors (#2278)

* mini fix (#2204)

* [chore] Bump fallback version to 44 (#2274)

* find recipe in theory working

* add some helper methods to inventory logics

* update deps

* use collect not toList

* fix loading crash

* fix complex processing logic using wrong find recipe

* fix up everything and get recipe finding working

* annotate and clean up methods

* spotless

* save things to nbt

* input separation for mutes and fully working processing

* apply mute mode on processing logic

* clean up overrides

---------

Co-authored-by: chochem <40274384+chochem@users.noreply.github.com>
Co-authored-by: miozune <miozune@gmail.com>
Co-authored-by: Pxx500 <81298696+Pxx500@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Glease <4586901+Glease@users.noreply.github.com>
Co-authored-by: firenoo <49818773+firenoo@users.noreply.github.com>
Co-authored-by: Martin Robertz <dream-master@gmx.net>
Co-authored-by: Harry <harryyunull@gmail.com>
Co-authored-by: Eraldoe <Eraldoe@users.noreply.github.com>
Co-authored-by: Jakub <53441451+kuba6000@users.noreply.github.com>
Co-authored-by: Connor-Colenso <52056774+Connor-Colenso@users.noreply.github.com>
Co-authored-by: Guillaume Mercier <C0bra5@users.noreply.github.com>
Co-authored-by: Fox_white <39846845+foxwhite25@users.noreply.github.com>
Co-authored-by: Alastors <78517796+Alastors@users.noreply.github.com>
Co-authored-by: Kyium <43573052+Kyium@users.noreply.github.com>
Co-authored-by: SKProCH <29896317+SKProCH@users.noreply.github.com>
Co-authored-by: Sampsa <69092953+S4mpsa@users.noreply.github.com>
Co-authored-by: Jaiden Baker <jaidencolebaker@gmail.com>

* address minecraft's reviews from #2283

* Refactor MuTE processing logic (#2301) -fix conflicts

* Fix void protection for mutes (#2298) - fix conflicts

* initial variables

* implement working void protection on items and fluids

* Adds a Simple PowerOutput task and cleans up some of the code. (#2303)

* create a power output task which can be used for dynamos

* refactor the controllers and clean up

* add some documentation to power logic

* make a wireless network manager class instead of using an interface

* clean up and add documentation.

* setAmperage to setMaxAmperage

* fix comment

* remove IGlobalWirelessEnergy usage

* getAmperage -> getMaxAmperage

* add todo for future

* Cleanup MuTEMaster code (#2282)

* exit early

* spotless

* better side checking

* make if blocks mutually exclusive

* more exit early

* convert nested ternary operators into if blocks

* remove dead code

* collapse nested if blocks

* add todo to break verylong condition into much smaller ones

* spotless apply

* collapsing nested if blocks and more exit early

* spotless apply

* extract try/catch block to its own utility method

* break down this unreadable condition

* boolean magic (1/5)

* boolean magic (2/5)

Also corrected some logic on the player null check, we want it to be non null

* boolean magic (3/5)

* boolean magic (4/5)

* boolean magic (5/5)

* remove todo

* Fix logic

---------

Co-authored-by: Jason Mitchell <jason@puzzle.io>
Co-authored-by: BlueWeabo <76872108+BlueWeabo@users.noreply.github.com>

* Clean up a lil bit and fix some issues with MuTEs (#2316)

* clean up fixes

* actual fix and remove useless if

* Rework MuTEGUI structure (#2429) - fix conflicts`

Merged to rebase MuTEMaster and fix any non-compile errors and game not launching in there

* some docs on a few methods

* innitial GUI class

* try to implement guis

* almost working - fix comflict

* add UIBuildContext to getGUI method

* make it compile

* sketch gui - fix conflict

* compile and spotless

* add config option to enabling MuTEs

* Spotless apply for branch feature/MuTEMaster for #2431 (#2432)

spotlessApply

Co-authored-by: GitHub GTNH Actions <>

* address reviews on broken processing logic

* spotless

* fix doc and review

---------

Co-authored-by: Maxim <maxim235@gmx.de>
Co-authored-by: RIONDY 'POPlol333' Adam <76914762+POPlol333@users.noreply.github.com>
Co-authored-by: Jason Mitchell <mitchej@gmail.com>
Co-authored-by: Daniel Mendes <70096037+Steelux8@users.noreply.github.com>
Co-authored-by: kstvr32 <109012629+kstvr32@users.noreply.github.com>
Co-authored-by: TheEpicGamer274 <102255081+TheEpicGamer274@users.noreply.github.com>
Co-authored-by: miozune <miozune@gmail.com>
Co-authored-by: chochem <40274384+chochem@users.noreply.github.com>
Co-authored-by: Pxx500 <81298696+Pxx500@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Glease <4586901+Glease@users.noreply.github.com>
Co-authored-by: firenoo <49818773+firenoo@users.noreply.github.com>
Co-authored-by: Martin Robertz <dream-master@gmx.net>
Co-authored-by: Harry <harryyunull@gmail.com>
Co-authored-by: Eraldoe <Eraldoe@users.noreply.github.com>
Co-authored-by: Jakub <53441451+kuba6000@users.noreply.github.com>
Co-authored-by: Connor-Colenso <52056774+Connor-Colenso@users.noreply.github.com>
Co-authored-by: Guillaume Mercier <C0bra5@users.noreply.github.com>
Co-authored-by: Fox_white <39846845+foxwhite25@users.noreply.github.com>
Co-authored-by: Alastors <78517796+Alastors@users.noreply.github.com>
Co-authored-by: Kyium <43573052+Kyium@users.noreply.github.com>
Co-authored-by: SKProCH <29896317+SKProCH@users.noreply.github.com>
Co-authored-by: Sampsa <69092953+S4mpsa@users.noreply.github.com>
Co-authored-by: Jaiden Baker <jaidencolebaker@gmail.com>
Co-authored-by: boubou19 <miisterunknown@gmail.com>
Co-authored-by: Jason Mitchell <jason@puzzle.io>
---
 .../multiblock/base/MultiBlockPart.java            | 688 ++++++++-------------
 1 file changed, 259 insertions(+), 429 deletions(-)

(limited to 'src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java')

diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java
index db9053b0d7..223edc0761 100644
--- a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java
@@ -2,7 +2,6 @@ package gregtech.api.multitileentity.multiblock.base;
 
 import static com.google.common.math.LongMath.log2;
 import static gregtech.api.enums.GT_Values.B;
-import static gregtech.api.enums.GT_Values.NBT;
 import static gregtech.api.enums.Textures.BlockIcons.FLUID_IN_SIGN;
 import static gregtech.api.enums.Textures.BlockIcons.FLUID_OUT_SIGN;
 import static gregtech.api.enums.Textures.BlockIcons.ITEM_IN_SIGN;
@@ -11,13 +10,17 @@ import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_ENERGY_IN_MULTI;
 import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_ENERGY_OUT_MULTI;
 import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_PIPE_IN;
 import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_PIPE_OUT;
-import static org.apache.commons.lang3.ObjectUtils.firstNonNull;
 
 import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.entity.player.EntityPlayerMP;
@@ -25,44 +28,42 @@ import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.ChunkCoordinates;
+import net.minecraft.util.StatCollector;
 import net.minecraftforge.common.util.ForgeDirection;
 import net.minecraftforge.fluids.Fluid;
-import net.minecraftforge.fluids.FluidStack;
-import net.minecraftforge.fluids.FluidTankInfo;
-import net.minecraftforge.fluids.IFluidTank;
 
-import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
-import com.gtnewhorizons.modularui.api.screen.ModularWindow;
 import com.gtnewhorizons.modularui.api.screen.ModularWindow.Builder;
 import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
 import com.gtnewhorizons.modularui.common.widget.DrawableWidget;
-import com.gtnewhorizons.modularui.common.widget.DropDownWidget;
-import com.gtnewhorizons.modularui.common.widget.FluidSlotWidget;
-import com.gtnewhorizons.modularui.common.widget.Scrollable;
-import com.gtnewhorizons.modularui.common.widget.SlotGroup;
-import com.gtnewhorizons.modularui.common.widget.SlotWidget;
-import com.gtnewhorizons.modularui.common.widget.TextWidget;
-
-import gregtech.api.enums.GT_Values;
+
+import gregtech.api.enums.GT_Values.NBT;
+import gregtech.api.enums.InventoryType;
 import gregtech.api.fluid.FluidTankGT;
-import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.gui.GUIHost;
+import gregtech.api.gui.GUIProvider;
 import gregtech.api.interfaces.ITexture;
+import gregtech.api.logic.FluidInventoryLogic;
+import gregtech.api.logic.ItemInventoryLogic;
+import gregtech.api.logic.NullPowerLogic;
 import gregtech.api.logic.PowerLogic;
 import gregtech.api.logic.interfaces.PowerLogicHost;
+import gregtech.api.multitileentity.MultiTileEntityRegistry;
 import gregtech.api.multitileentity.base.NonTickableMultiTileEntity;
+import gregtech.api.multitileentity.enums.MultiTileCasingPurpose;
 import gregtech.api.multitileentity.interfaces.IMultiBlockController;
 import gregtech.api.multitileentity.interfaces.IMultiBlockPart;
-import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_BreakBlock;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
 import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_HasModes;
 import gregtech.api.net.GT_Packet_MultiTileEntity;
 import gregtech.api.render.TextureFactory;
 import gregtech.api.util.GT_Utility;
 import gregtech.common.covers.CoverInfo;
+import gregtech.common.gui.PartGUIProvider;
 import mcp.mobius.waila.api.IWailaConfigHandler;
 import mcp.mobius.waila.api.IWailaDataAccessor;
 
 public abstract class MultiBlockPart extends NonTickableMultiTileEntity
-    implements IMultiBlockPart, IMTE_BreakBlock, IMTE_HasModes, PowerLogicHost {
+    implements IMultiBlockPart, IMTE_HasModes, PowerLogicHost, IMultiTileEntity.IMTE_AddToolTips, GUIHost {
 
     public static final int NOTHING = 0, ENERGY_IN = B[0], ENERGY_OUT = B[1], FLUID_IN = B[2], FLUID_OUT = B[3],
         ITEM_IN = B[4], ITEM_OUT = B[5];
@@ -70,16 +71,20 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
     protected final List<Integer> BASIC_MODES = new ArrayList<>(
         Arrays.asList(NOTHING, ENERGY_IN, ENERGY_OUT, FLUID_IN, FLUID_OUT, ITEM_IN, ITEM_OUT));
 
-    protected ChunkCoordinates mTargetPos = null;
-    protected IMultiBlockController target = null;
+    protected Set<MultiTileCasingPurpose> registeredPurposes = new HashSet<>();
 
-    protected int mAllowedModes = NOTHING; // BITMASK - Modes allowed for this part
-    protected byte mMode = 0; // Mode selected for this part
+    protected ChunkCoordinates targetPosition = null;
 
-    protected String mLockedInventory = GT_Values.E;
+    protected int allowedModes = NOTHING; // BITMASK - Modes allowed for this part
+    protected int mode = 0; // Mode selected for this part
+
+    protected UUID lockedInventory;
     protected int mLockedInventoryIndex = 0;
     protected FluidTankGT configurationTank = new FluidTankGT();
 
+    @Nonnull
+    protected final GUIProvider<?> guiProvider = createGUIProvider();
+
     /**
      * What Part Tier is this part? All Basic Casings are Tier 1, and will allow: Energy, Item, Fluid input/output. Some
      * of the more advanced modes can be set to require a higher tier part.
@@ -88,33 +93,40 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
         return 1;
     }
 
-    public String getLockedInventory() {
-        // TODO: Can this cause side-effects? Removed for now because it causes huge network traffic when using covers
-        // issueClientUpdate();
-        IMultiBlockController controller = getTarget(false);
-        if (modeSelected(ITEM_IN) || modeSelected(ITEM_OUT)) {
-            if (!getNameOfInventoryFromIndex(controller, mLockedInventoryIndex).equals(mLockedInventory)) {
-                mLockedInventory = getNameOfInventoryFromIndex(controller, mLockedInventoryIndex);
-                if (mLockedInventory.equals(Controller.ALL_INVENTORIES_NAME)) {
-                    mLockedInventory = "";
-                }
-            }
-        } else {
-            if (!getNameOfTankArrayFromIndex(controller, mLockedInventoryIndex).equals(mLockedInventory)) {
-                mLockedInventory = getNameOfTankArrayFromIndex(controller, mLockedInventoryIndex);
-                if (mLockedInventory.equals(Controller.ALL_INVENTORIES_NAME)) {
-                    mLockedInventory = "";
-                }
+    @Override
+    public UUID getLockedInventory() {
+        return lockedInventory;
+    }
+
+    public void setTarget(IMultiBlockController newTarget, int aAllowedModes) {
+        IMultiBlockController currentTarget = getTarget(false);
+        if (currentTarget != null && currentTarget != newTarget) {
+            for (MultiTileCasingPurpose purpose : registeredPurposes) {
+                unregisterPurpose(purpose);
             }
         }
-        return mLockedInventory.equals("") ? null : mLockedInventory;
+        targetPosition = (newTarget == null ? null : newTarget.getCoords());
+        allowedModes = aAllowedModes;
+        if (newTarget != null) {
+            registerCovers(newTarget);
+            registerPurposes();
+        }
     }
 
-    public void setTarget(IMultiBlockController aTarget, int aAllowedModes) {
-        target = aTarget;
-        mTargetPos = (target == null ? null : target.getCoords());
-        mAllowedModes = aAllowedModes;
-        if (target != null) registerCovers(target);
+    protected void registerPurpose(MultiTileCasingPurpose purpose) {
+        IMultiBlockController target = getTarget(false);
+        if (target != null) {
+            target.registerCaseWithPurpose(purpose, this);
+            registeredPurposes.add(purpose);
+        }
+    }
+
+    protected void unregisterPurpose(MultiTileCasingPurpose purpose) {
+        IMultiBlockController target = getTarget(false);
+        if (target != null) {
+            target.unregisterCaseWithPurpose(purpose, this);
+        }
+        registeredPurposes.remove(purpose);
     }
 
     @Override
@@ -125,14 +137,14 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
         } else {
             tList.add("No Controller");
         }
-        tList.add("Casing Mode: " + getModeName(mMode));
+        tList.add("Casing Mode: " + getModeName(mode));
     }
 
     @Override
     public void getWailaBody(ItemStack itemStack, List<String> currentTip, IWailaDataAccessor accessor,
         IWailaConfigHandler config) {
         super.getWailaBody(itemStack, currentTip, accessor, config);
-        currentTip.add(String.format("Mode: %s", getModeName(mMode)));
+        currentTip.add(String.format("Mode: %s", getModeName(mode)));
         if (modeSelected(FLUID_OUT)) {
             if (configurationTank != null && configurationTank.get() != null) {
                 currentTip.add(
@@ -147,19 +159,22 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
     }
 
     public IMultiBlockController getTarget(boolean aCheckValidity) {
-        if (mTargetPos == null) return null;
-        if (target == null || target.isDead()) {
-            if (worldObj.blockExists(mTargetPos.posX, mTargetPos.posY, mTargetPos.posZ)) {
-                final TileEntity te = worldObj.getTileEntity(mTargetPos.posX, mTargetPos.posY, mTargetPos.posZ);
-                if (te instanceof IMultiBlockController) {
-                    target = (IMultiBlockController) te;
-                    // Register our covers with the controller
-                    registerCovers(target);
-                } else {
-                    mTargetPos = null;
-                }
-            }
+        if (targetPosition == null) {
+            return null;
+        }
+
+        if (!worldObj.blockExists(targetPosition.posX, targetPosition.posY, targetPosition.posZ)) {
+            return null;
+        }
+        final TileEntity te = worldObj.getTileEntity(targetPosition.posX, targetPosition.posY, targetPosition.posZ);
+        IMultiBlockController target = null;
+        if (te instanceof IMultiBlockController targetFound) {
+            target = targetFound;
+        } else {
+            targetPosition = null;
+            return null;
         }
+
         if (aCheckValidity) {
             return target != null && target.checkStructure(false) ? target : null;
         }
@@ -175,17 +190,26 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
         }
     }
 
+    protected void registerPurposes() {
+        for (MultiTileCasingPurpose purpose : registeredPurposes) {
+            registerPurpose(purpose);
+        }
+    }
+
     @Override
     public void setCoverItemAtSide(ForgeDirection side, ItemStack aCover) {
         super.setCoverItemAtSide(side, aCover);
         // TODO: Filter on tickable covers
         final IMultiBlockController tTarget = getTarget(true);
-        if (tTarget != null) {
-            final CoverInfo coverInfo = getCoverInfoAtSide(side);
-            if (coverInfo.isValid() && coverInfo.getTickRate() > 0) {
-                tTarget.registerCoveredPartOnSide(side, this);
-            }
+        if (tTarget == null) {
+            return;
         }
+
+        final CoverInfo coverInfo = getCoverInfoAtSide(side);
+        if (coverInfo.isValid() && coverInfo.getTickRate() > 0) {
+            tTarget.registerCoveredPartOnSide(side, this);
+        }
+
     }
 
     public void unregisterCovers(IMultiBlockController controller) {
@@ -208,16 +232,16 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
 
     @Override
     public void readMultiTileNBT(NBTTagCompound aNBT) {
-        if (aNBT.hasKey(NBT.ALLOWED_MODES)) mAllowedModes = aNBT.getInteger(NBT.ALLOWED_MODES);
-        if (aNBT.hasKey(NBT.MODE)) mMode = aNBT.getByte(NBT.MODE);
+        if (aNBT.hasKey(NBT.ALLOWED_MODES)) allowedModes = aNBT.getInteger(NBT.ALLOWED_MODES);
+        if (aNBT.hasKey(NBT.MODE)) setMode(aNBT.getByte(NBT.MODE));
         if (aNBT.hasKey(NBT.TARGET)) {
-            mTargetPos = new ChunkCoordinates(
+            targetPosition = new ChunkCoordinates(
                 aNBT.getInteger(NBT.TARGET_X),
                 aNBT.getShort(NBT.TARGET_Y),
                 aNBT.getInteger(NBT.TARGET_Z));
         }
         if (aNBT.hasKey(NBT.LOCKED_INVENTORY)) {
-            mLockedInventory = aNBT.getString(NBT.LOCKED_INVENTORY);
+            lockedInventory = UUID.fromString(aNBT.getString(NBT.LOCKED_INVENTORY));
         }
         if (aNBT.hasKey(NBT.LOCKED_INVENTORY_INDEX)) {
             mLockedInventoryIndex = aNBT.getInteger(NBT.LOCKED_INVENTORY_INDEX);
@@ -225,20 +249,26 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
         if (aNBT.hasKey(NBT.LOCKED_FLUID)) {
             configurationTank.readFromNBT(aNBT, NBT.LOCKED_FLUID);
         }
+        if (modeSelected(ITEM_OUT)) {
+            registeredPurposes.add(MultiTileCasingPurpose.ItemOutput);
+        }
+        if (modeSelected(FLUID_OUT)) {
+            registeredPurposes.add(MultiTileCasingPurpose.FluidOutput);
+        }
     }
 
     @Override
     public void writeMultiTileNBT(NBTTagCompound aNBT) {
-        if (mAllowedModes != NOTHING) aNBT.setInteger(NBT.ALLOWED_MODES, mAllowedModes);
-        if (mMode != 0) aNBT.setInteger(NBT.MODE, mMode);
-        if (mTargetPos != null) {
+        if (allowedModes != NOTHING) aNBT.setInteger(NBT.ALLOWED_MODES, allowedModes);
+        if (mode != 0) aNBT.setInteger(NBT.MODE, mode);
+        if (targetPosition != null) {
             aNBT.setBoolean(NBT.TARGET, true);
-            aNBT.setInteger(NBT.TARGET_X, mTargetPos.posX);
-            aNBT.setShort(NBT.TARGET_Y, (short) mTargetPos.posY);
-            aNBT.setInteger(NBT.TARGET_Z, mTargetPos.posZ);
+            aNBT.setInteger(NBT.TARGET_X, targetPosition.posX);
+            aNBT.setShort(NBT.TARGET_Y, (short) targetPosition.posY);
+            aNBT.setInteger(NBT.TARGET_Z, targetPosition.posZ);
         }
-        if (mLockedInventory != null) {
-            aNBT.setString(NBT.LOCKED_INVENTORY, mLockedInventory);
+        if (lockedInventory != null) {
+            aNBT.setString(NBT.LOCKED_INVENTORY, lockedInventory.toString());
         }
         if (mLockedInventoryIndex != 0) {
             aNBT.setInteger(NBT.LOCKED_INVENTORY_INDEX, mLockedInventoryIndex);
@@ -270,34 +300,47 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
 
     @Override
     public void setTargetPos(ChunkCoordinates aTargetPos) {
-        mTargetPos = aTargetPos;
-        IMultiBlockController mTarget = getTarget(false);
-        setTarget(mTarget, mAllowedModes);
+        targetPosition = aTargetPos;
+        IMultiBlockController target = getTarget(false);
+        setTarget(target, allowedModes);
     }
 
     @Override
     public ChunkCoordinates getTargetPos() {
-        return mTargetPos;
+        return targetPosition;
     }
 
     @Override
-    public void setMode(byte aMode) {
-        mMode = aMode;
+    public void setMode(int mode) {
+        if (this.mode == mode) return;
+        if (modeSelected(FLUID_OUT)) {
+            unregisterPurpose(MultiTileCasingPurpose.FluidOutput);
+        }
+        if (modeSelected(ITEM_OUT)) {
+            unregisterPurpose(MultiTileCasingPurpose.ItemOutput);
+        }
+        this.mode = mode;
+        if (modeSelected(FLUID_OUT)) {
+            registerPurpose(MultiTileCasingPurpose.FluidOutput);
+        }
+        if (modeSelected(ITEM_OUT)) {
+            registerPurpose(MultiTileCasingPurpose.ItemOutput);
+        }
     }
 
     @Override
-    public byte getMode() {
-        return mMode;
+    public int getMode() {
+        return mode;
     }
 
     @Override
     public int getAllowedModes() {
-        return mAllowedModes;
+        return allowedModes;
     }
 
     @Override
     public void setAllowedModes(int aAllowedModes) {
-        mAllowedModes = aAllowedModes;
+        allowedModes = aAllowedModes;
     }
 
     /**
@@ -305,7 +348,7 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
      */
     public boolean hasMode(int aMode) {
         // This is not sent to the client
-        return (mAllowedModes & aMode) != 0;
+        return (allowedModes & aMode) != 0;
     }
 
     /**
@@ -313,7 +356,7 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
      */
     public boolean modeSelected(int... aModes) {
         for (int aMode : aModes) {
-            if (hasMode(aMode) && mMode == getModeOrdinal(aMode)) return true;
+            if (hasMode(aMode) && mode == getModeOrdinal(aMode)) return true;
         }
         return false;
     }
@@ -344,39 +387,39 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
     @Override
     public ITexture getTexture(ForgeDirection side) {
         ITexture texture = super.getTexture(side);
-        if (mMode != 0 && side == facing) {
-            if (mMode == getModeOrdinal(ITEM_IN)) {
+        if (mode != 0 && side == facing) {
+            if (mode == getModeOrdinal(ITEM_IN)) {
                 return TextureFactory.of(
                     texture,
                     TextureFactory.of(OVERLAY_PIPE_IN),
                     TextureFactory.of(ITEM_IN_SIGN),
                     getCoverTexture(side));
             }
-            if (mMode == getModeOrdinal(ITEM_OUT)) {
+            if (mode == getModeOrdinal(ITEM_OUT)) {
                 return TextureFactory.of(
                     texture,
                     TextureFactory.of(OVERLAY_PIPE_OUT),
                     TextureFactory.of(ITEM_OUT_SIGN),
                     getCoverTexture(side));
             }
-            if (mMode == getModeOrdinal(FLUID_IN)) {
+            if (mode == getModeOrdinal(FLUID_IN)) {
                 return TextureFactory.of(
                     texture,
                     TextureFactory.of(OVERLAY_PIPE_IN),
                     TextureFactory.of(FLUID_IN_SIGN),
                     getCoverTexture(side));
             }
-            if (mMode == getModeOrdinal(FLUID_OUT)) {
+            if (mode == getModeOrdinal(FLUID_OUT)) {
                 return TextureFactory.of(
                     texture,
                     TextureFactory.of(OVERLAY_PIPE_OUT),
                     TextureFactory.of(FLUID_OUT_SIGN),
                     getCoverTexture(side));
             }
-            if (mMode == getModeOrdinal(ENERGY_IN)) {
+            if (mode == getModeOrdinal(ENERGY_IN)) {
                 return TextureFactory.of(texture, TextureFactory.of(OVERLAY_ENERGY_IN_MULTI), getCoverTexture(side));
             }
-            if (mMode == getModeOrdinal(ENERGY_OUT)) {
+            if (mode == getModeOrdinal(ENERGY_OUT)) {
                 return TextureFactory.of(texture, TextureFactory.of(OVERLAY_ENERGY_OUT_MULTI), getCoverTexture(side));
             }
         }
@@ -384,11 +427,6 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
         return TextureFactory.of(texture, getCoverTexture(side));
     }
 
-    @Override
-    public boolean isUseableByPlayer(EntityPlayer entityPlayer) {
-        return false;
-    }
-
     protected String getModeName(int aMode) {
         if (aMode == NOTHING) return "Nothing";
         if (aMode == getModeOrdinal(ITEM_IN)) return "Item Input";
@@ -407,11 +445,11 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
     }
 
     protected byte getNextAllowedMode(List<Integer> allowedModes) {
-        if (mAllowedModes == NOTHING) return NOTHING;
+        if (this.allowedModes == NOTHING) return NOTHING;
 
         final int numModes = allowedModes.size();
         for (byte i = 1; i <= numModes; i++) {
-            final byte curMode = (byte) ((mMode + i) % numModes);
+            final byte curMode = (byte) ((mode + i) % numModes);
             if (curMode == NOTHING || hasMode(1 << (curMode - 1))) return curMode;
         }
         // Nothing valid found
@@ -420,16 +458,16 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
 
     @Override
     public boolean onMalletRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
-        float aY, float aZ, ItemStack aTool) {
-        if (mAllowedModes == NOTHING) return true;
-        if (mMode == NOTHING) {
+        float aY, float aZ) {
+        if (allowedModes == NOTHING) return true;
+        if (mode == NOTHING) {
             facing = wrenchSide;
         }
-        mMode = getNextAllowedMode(BASIC_MODES);
+        setMode(getNextAllowedMode(BASIC_MODES));
         if (aPlayer.isSneaking()) {
             facing = wrenchSide;
         }
-        GT_Utility.sendChatToPlayer(aPlayer, "Mode set to `" + getModeName(mMode) + "' (" + mMode + ")");
+        GT_Utility.sendChatToPlayer(aPlayer, "Mode set to `" + getModeName(mode) + "' (" + mode + ")");
         sendClientData((EntityPlayerMP) aPlayer);
         return true;
     }
@@ -447,109 +485,50 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
         return "gt.multitileentity.multiblock.part";
     }
 
+    @Override
+    public boolean shouldTick(long tickTimer) {
+        return modeSelected(ITEM_OUT, FLUID_OUT);
+    }
+
     /**
      * TODO: Make sure the energy/item/fluid hatch is facing that way! or has that mode enabled on that side Check
      * SIDE_UNKNOWN for or coverbehavior
      */
 
-    /**
-     * Fluid - Depending on the part type - proxy it to the multiblock controller, if we have one
-     */
+    // #region Fluid - Depending on the part type - proxy it to the multiblock controller, if we have one
     @Override
-    public int fill(ForgeDirection aDirection, FluidStack aFluidStack, boolean aDoFill) {
-        if (!modeSelected(FLUID_IN)) return 0;
+    @Nullable
+    public FluidInventoryLogic getFluidLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type) {
+        if (side != facing && side != ForgeDirection.UNKNOWN) return null;
 
-        if (aFluidStack == null || isWrongFluid(aFluidStack.getFluid())) return 0;
-        if (aDirection != ForgeDirection.UNKNOWN
-            && (facing.compareTo(aDirection) != 0 || !coverLetsFluidIn(aDirection, aFluidStack.getFluid()))) return 0;
-        final IMultiBlockController controller = getTarget(true);
-        return controller == null ? 0 : controller.fill(this, aDirection, aFluidStack, aDoFill);
-    }
+        if (!modeSelected(FLUID_IN, FLUID_OUT)) return null;
 
-    @Override
-    public FluidStack drain(ForgeDirection aDirection, FluidStack aFluidStack, boolean aDoDrain) {
-        if (!modeSelected(FLUID_OUT)) return null;
-        if (aFluidStack == null || isWrongFluid(aFluidStack.getFluid())) return null;
-        if (aDirection != ForgeDirection.UNKNOWN
-            && (facing.compareTo(aDirection) != 0 || !coverLetsFluidOut(aDirection, aFluidStack.getFluid())))
-            return null;
-        final IMultiBlockController controller = getTarget(true);
-        return controller == null ? null : controller.drain(this, aDirection, aFluidStack, aDoDrain);
-    }
-
-    @Override
-    public FluidStack drain(ForgeDirection aDirection, int aAmountToDrain, boolean aDoDrain) {
-        if (!modeSelected(FLUID_OUT)) return null;
-        final IMultiBlockController controller = getTarget(true);
+        IMultiBlockController controller = getTarget(false);
         if (controller == null) return null;
-        FluidStack aFluidStack = null;
-        if (getLockedFluid() != null) {
-            aFluidStack = controller.getDrainableFluid(aDirection, getLockedFluid());
-        } else {
-            aFluidStack = controller.getDrainableFluid(aDirection);
-        }
-        if (aFluidStack == null || isWrongFluid(aFluidStack.getFluid())) return null;
-        if (aDirection != ForgeDirection.UNKNOWN
-            && (facing.compareTo(aDirection) != 0 || !coverLetsFluidOut(aDirection, aFluidStack.getFluid())))
-            return null;
-        return controller.drain(this, aDirection, aFluidStack, aDoDrain);
-    }
-
-    @Override
-    public boolean canFill(ForgeDirection aDirection, Fluid aFluid) {
-        if (!modeSelected(FLUID_IN)) return false;
-
-        if (aDirection != ForgeDirection.UNKNOWN
-            && (facing.compareTo(aDirection) != 0 || !coverLetsFluidIn(aDirection, aFluid))) return false;
-        if (isWrongFluid(aFluid)) return false;
-        final IMultiBlockController controller = getTarget(true);
-        return controller != null && controller.canFill(this, aDirection, aFluid);
+        return controller
+            .getFluidLogic(modeSelected(FLUID_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory);
     }
 
-    @Override
-    public boolean canDrain(ForgeDirection aDirection, Fluid aFluid) {
-        if (!modeSelected(FLUID_OUT)) return false;
-        if (aDirection != ForgeDirection.UNKNOWN
-            && (facing.compareTo(aDirection) != 0 || !coverLetsFluidOut(aDirection, aFluid))) return false;
-        if (isWrongFluid(aFluid)) return false;
-        final IMultiBlockController controller = getTarget(true);
-        return controller != null && controller.canDrain(this, aDirection, aFluid);
-    }
-
-    @Override
-    public FluidTankInfo[] getTankInfo(ForgeDirection aDirection) {
-        if (!modeSelected(FLUID_IN, FLUID_OUT)
-            || (aDirection != ForgeDirection.UNKNOWN && facing.compareTo(aDirection) != 0))
-            return GT_Values.emptyFluidTankInfo;
-        final IMultiBlockController controller = getTarget(true);
-        if (controller == null) return GT_Values.emptyFluidTankInfo;
-
-        final CoverInfo coverInfo = getCoverInfoAtSide(aDirection);
-
-        if ((controller.isLiquidInput(aDirection) && coverInfo.letsFluidIn(null, controller))
-            || (controller.isLiquidOutput(aDirection) && coverInfo.letsFluidOut(null, controller)))
-            return controller.getTankInfo(this, aDirection);
-
-        return GT_Values.emptyFluidTankInfo;
-    }
+    // #endregion Fluid
 
     // #region Energy - Depending on the part type - proxy to the multiblock controller, if we have one
 
     @Override
-    public PowerLogic getPowerLogic(ForgeDirection side) {
-        if (facing == side) {
-            return null;
+    @Nonnull
+    public PowerLogic getPowerLogic(@Nonnull ForgeDirection side) {
+        if (side != facing && side != ForgeDirection.UNKNOWN) {
+            return new NullPowerLogic();
         }
 
         if (!modeSelected(ENERGY_IN, ENERGY_OUT)) {
-            return null;
+            return new NullPowerLogic();
         }
 
         final IMultiBlockController controller = getTarget(true);
         if (controller == null) {
-            return null;
+            return new NullPowerLogic();
         }
-        return controller.getPowerLogic(this, side);
+        return controller.getPowerLogic();
     }
 
     @Override
@@ -562,119 +541,32 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
         return modeSelected(ENERGY_OUT);
     }
 
-    // #endregion
-
-    /**
-     * Inventory - Depending on the part type - proxy to the multiblock controller, if we have one
-     */
-    @Override
-    public boolean hasInventoryBeenModified() {
-        final IMultiBlockController controller = getTarget(true);
-        return (controller != null && controller.hasInventoryBeenModified(this));
-    }
-
-    @Override
-    public boolean isValidSlot(int aIndex) {
-        final IMultiBlockController controller = getTarget(true);
-        return (controller != null && controller.isValidSlot(this, aIndex));
-    }
-
-    @Override
-    public boolean addStackToSlot(int aIndex, ItemStack aStack) {
-        if (!modeSelected(ITEM_IN, ITEM_OUT)) return false;
-        final IMultiBlockController controller = getTarget(true);
-        return (controller != null && controller.addStackToSlot(this, aIndex, aStack));
-    }
-
-    @Override
-    public boolean addStackToSlot(int aIndex, ItemStack aStack, int aAmount) {
-        if (!modeSelected(ITEM_IN, ITEM_OUT)) return false;
-        final IMultiBlockController controller = getTarget(true);
-        return (controller != null && controller.addStackToSlot(this, aIndex, aStack, aAmount));
-    }
+    // #endregion Energy
 
-    @Override
-    public int[] getAccessibleSlotsFromSide(int ordinalSide) {
-        final ForgeDirection side = ForgeDirection.getOrientation(ordinalSide);
-        if (!modeSelected(ITEM_IN, ITEM_OUT) || (facing != ForgeDirection.UNKNOWN && facing.compareTo(side) != 0))
-            return GT_Values.emptyIntArray;
-        final IMultiBlockController controller = getTarget(true);
-        return controller != null ? controller.getAccessibleSlotsFromSide(this, side) : GT_Values.emptyIntArray;
-    }
+    // #region Item - Depending on the part type - proxy to the multiblock controller, if we have one
 
     @Override
-    public boolean canInsertItem(int aSlot, ItemStack aStack, int ordinalSide) {
-        final ForgeDirection side = ForgeDirection.getOrientation(ordinalSide);
-        if (!modeSelected(ITEM_IN, ITEM_OUT)
-            || (facing != ForgeDirection.UNKNOWN && (facing.compareTo(side) != 0 || !coverLetsItemsIn(side, aSlot))))
-            return false;
-        final IMultiBlockController controller = getTarget(true);
-        return (controller != null && controller.canInsertItem(this, aSlot, aStack, side));
-    }
+    @Nullable
+    public ItemInventoryLogic getItemLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType unused) {
+        if (side != facing && side != ForgeDirection.UNKNOWN) return null;
 
-    @Override
-    public boolean canExtractItem(int aSlot, ItemStack aStack, int ordinalSide) {
-        final ForgeDirection side = ForgeDirection.getOrientation(ordinalSide);
-        if (!modeSelected(ITEM_IN, ITEM_OUT)
-            || (facing != ForgeDirection.UNKNOWN && (facing.compareTo(side) != 0 || !coverLetsItemsOut(side, aSlot))))
-            return false;
-        final IMultiBlockController controller = getTarget(true);
-        return (controller != null && controller.canExtractItem(this, aSlot, aStack, side));
-    }
-
-    @Override
-    public int getSizeInventory() {
-        if (!modeSelected(ITEM_IN, ITEM_OUT)) return 0;
-        final IMultiBlockController controller = getTarget(true);
-        return controller != null ? controller.getSizeInventory(this) : 0;
-    }
-
-    @Override
-    public ItemStack getStackInSlot(int aSlot) {
         if (!modeSelected(ITEM_IN, ITEM_OUT)) return null;
-        final IMultiBlockController controller = getTarget(true);
-        return controller != null ? controller.getStackInSlot(this, aSlot) : null;
-    }
-
-    @Override
-    public ItemStack decrStackSize(int aSlot, int aDecrement) {
-        if (!modeSelected(ITEM_IN, ITEM_OUT)) return null;
-        final IMultiBlockController controller = getTarget(true);
-        return controller != null ? controller.decrStackSize(this, aSlot, aDecrement) : null;
-    }
 
-    @Override
-    public ItemStack getStackInSlotOnClosing(int aSlot) {
-        final IMultiBlockController controller = getTarget(true);
-        return controller != null ? controller.getStackInSlotOnClosing(this, aSlot) : null;
-    }
-
-    @Override
-    public void setInventorySlotContents(int aSlot, ItemStack aStack) {
-        final IMultiBlockController controller = getTarget(true);
-        if (controller != null) controller.setInventorySlotContents(this, aSlot, aStack);
-    }
-
-    @Override
-    public String getInventoryName() {
-        final IMultiBlockController controller = getTarget(true);
-        if (controller != null) return controller.getInventoryName(this);
-        return firstNonNull(getCustomName(), getTileEntityName());
-    }
+        final IMultiBlockController controller = getTarget(false);
+        if (controller == null) return null;
 
-    @Override
-    public int getInventoryStackLimit() {
-        final IMultiBlockController controller = getTarget(true);
-        return controller != null ? controller.getInventoryStackLimit(this) : 0;
+        return controller
+            .getItemLogic(modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory);
     }
 
     @Override
-    public boolean isItemValidForSlot(int aSlot, ItemStack aStack) {
-        final IMultiBlockController controller = getTarget(true);
-        return controller != null && controller.isItemValidForSlot(this, aSlot, aStack);
+    @Nullable
+    public InventoryType getItemInventoryType() {
+        if (!modeSelected(ITEM_IN, ITEM_OUT)) return InventoryType.Both;
+        return modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output;
     }
 
-    // End Inventory
+    // #endregion Item
 
     // === Modular UI ===
     @Override
@@ -700,62 +592,6 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
         return getTarget(true) != null;
     }
 
-    protected void addItemInventory(Builder builder, UIBuildContext buildContext) {
-        final IMultiBlockController controller = getTarget(false);
-        if (controller == null) {
-            return;
-        }
-        final IItemHandlerModifiable inv = controller.getInventoryForGUI(this);
-        final Scrollable scrollable = new Scrollable().setVerticalScroll();
-        for (int rows = 0; rows * 4 < Math.min(inv.getSlots(), 128); rows++) {
-            int columnsToMake = Math.min(Math.min(inv.getSlots(), 128) - rows * 4, 4);
-            for (int column = 0; column < columnsToMake; column++) {
-                scrollable.widget(
-                    new SlotWidget(inv, rows * 4 + column).setPos(column * 18, rows * 18)
-                        .setSize(18, 18));
-            }
-        }
-        builder.widget(
-            scrollable.setSize(18 * 4 + 4, 18 * 4)
-                .setPos(52, 18));
-        DropDownWidget dropDown = new DropDownWidget();
-        dropDown.addDropDownItemsSimple(
-            controller.getInventoryNames(this),
-            (buttonWidget, index, label, setSelected) -> buttonWidget.setOnClick((clickData, widget) -> {
-                if (getNameOfInventoryFromIndex(controller, index).equals(Controller.ALL_INVENTORIES_NAME)) {
-                    mLockedInventory = GT_Values.E;
-                    mLockedInventoryIndex = 0;
-                } else {
-                    mLockedInventory = getNameOfInventoryFromIndex(controller, index);
-                    mLockedInventoryIndex = index;
-                }
-                setSelected.run();
-            }),
-            true);
-        builder.widget(
-            dropDown.setSelected(mLockedInventoryIndex)
-                .setExpandedMaxHeight(60)
-                .setDirection(DropDownWidget.Direction.DOWN)
-                .setPos(53, 5)
-                .setSize(70, 11));
-    }
-
-    protected String getNameOfInventoryFromIndex(final IMultiBlockController controller, int index) {
-        final List<String> invNames = controller.getInventoryIDs(this);
-        if (index > invNames.size()) {
-            return invNames.get(0);
-        }
-        return invNames.get(index);
-    }
-
-    protected String getNameOfTankArrayFromIndex(final IMultiBlockController controller, int index) {
-        final List<String> tankNames = controller.getTankArrayIDs(this);
-        if (index > tankNames.size()) {
-            return tankNames.get(0);
-        }
-        return tankNames.get(index);
-    }
-
     protected boolean isWrongFluid(Fluid fluid) {
         if (fluid == null) {
             return true;
@@ -776,98 +612,34 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
         return null;
     }
 
-    protected void addFluidInventory(Builder builder, UIBuildContext buildContext) {
-        final IMultiBlockController controller = getTarget(false);
+    @Override
+    public void addUIWidgets(Builder builder, UIBuildContext buildContext) {
+        super.addUIWidgets(builder, buildContext);
+        IMultiBlockController controller = getTarget(false);
         if (controller == null) {
             return;
         }
-        builder.widget(
-            new DrawableWidget().setDrawable(GT_UITextures.PICTURE_SCREEN_BLACK)
-                .setPos(7, 4)
-                .setSize(85, 95));
-        if (modeSelected(FLUID_OUT)) {
+        if ((modeSelected(ITEM_IN, ITEM_OUT))) {
             builder.widget(
-                new DrawableWidget().setDrawable(GT_UITextures.PICTURE_SCREEN_BLACK)
-                    .setPos(getGUIWidth() - 77, 4)
-                    .setSize(70, 40))
-                .widget(
-                    new TextWidget("Locked Fluid").setDefaultColor(COLOR_TEXT_WHITE.get())
-                        .setPos(getGUIWidth() - 72, 8));
-        }
-        final IFluidTank[] tanks = controller.getFluidTanksForGUI(this);
-        final Scrollable scrollable = new Scrollable().setVerticalScroll();
-        for (int rows = 0; rows * 4 < tanks.length; rows++) {
-            int columnsToMake = Math.min(tanks.length - rows * 4, 4);
-            for (int column = 0; column < columnsToMake; column++) {
-                FluidSlotWidget fluidSlot = new FluidSlotWidget(tanks[rows * 4 + column]);
-                if (modeSelected(FLUID_OUT)) {
-                    fluidSlot.setInteraction(true, false);
-                }
-                scrollable.widget(
-                    fluidSlot.setPos(column * 18, rows * 18)
-                        .setSize(18, 18));
-            }
+                controller
+                    .getItemLogic(modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory)
+                    .getGuiPart()
+                    .setSize(18 * 4 + 4, 18 * 5)
+                    .setPos(52, 7));
         }
-        builder.widget(
-            scrollable.setSize(18 * 4 + 4, 18 * 4)
-                .setPos(12, 21));
-        DropDownWidget dropDown = new DropDownWidget();
-        dropDown.addDropDownItemsSimple(
-            controller.getTankArrayNames(this),
-            (buttonWidget, index, label, setSelected) -> buttonWidget.setOnClick((clickData, widget) -> {
-                if (getNameOfTankArrayFromIndex(controller, index).equals(Controller.ALL_INVENTORIES_NAME)) {
-                    mLockedInventory = GT_Values.E;
-                    mLockedInventoryIndex = 0;
-                } else {
-                    mLockedInventory = getNameOfTankArrayFromIndex(controller, index);
-                    mLockedInventoryIndex = index;
-                }
-                setSelected.run();
-            }),
-            true);
-        builder.widget(
-            dropDown.setSelected(mLockedInventoryIndex)
-                .setExpandedMaxHeight(60)
-                .setDirection(DropDownWidget.Direction.DOWN)
-                .setPos(13, 8)
-                .setSize(70, 11));
-    }
 
-    @Override
-    public void addUIWidgets(Builder builder, UIBuildContext buildContext) {
-        if (modeSelected(ITEM_IN, ITEM_OUT)) {
-            addItemInventory(builder, buildContext);
-            return;
-        }
-        if (modeSelected(FLUID_IN, FLUID_OUT)) {
-            addFluidInventory(builder, buildContext);
-            if (modeSelected(FLUID_OUT)) {
-                builder.widget(
-                    SlotGroup.ofFluidTanks(Collections.singletonList(configurationTank), 1)
-                        .startFromSlot(0)
-                        .endAtSlot(0)
-                        .phantom(true)
-                        .build()
-                        .setPos(getGUIWidth() - 72, 20));
-            }
-            return;
+        if ((modeSelected(FLUID_IN, FLUID_OUT))) {
+            builder.widget(
+                controller
+                    .getFluidLogic(modeSelected(FLUID_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory)
+                    .getGuiPart()
+                    .setSize(18 * 4 + 4, 18 * 5)
+                    .setPos(52, 7));
         }
     }
 
-    @Override
-    public ModularWindow createWindow(UIBuildContext buildContext) {
-        if (isServerSide()) {
-            issueClientUpdate();
-        }
-        System.out.println("MultiBlockPart::createWindow");
-        if (modeSelected(NOTHING, ENERGY_IN, ENERGY_OUT) || mMode == NOTHING) {
-            IMultiBlockController controller = getTarget(false);
-            if (controller == null) {
-                return super.createWindow(buildContext);
-            }
-            return controller.createWindowGUI(buildContext);
-        }
-        return super.createWindow(buildContext);
+    protected boolean canOpenControllerGui() {
+        return true;
     }
 
     @Override
@@ -891,4 +663,62 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity
             super.addGregTechLogo(builder);
         }
     }
+
+    @Override
+    public void addToolTips(List<String> list, ItemStack stack, boolean f3_h) {
+        list.add("A MultiTileEntity Casing");
+    }
+
+    public String getInventoryName() {
+        IMultiBlockController controller = getTarget(false);
+        if (controller == null) return "";
+        if (modeSelected(ITEM_IN, ITEM_OUT)) {
+            InventoryType type = modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output;
+            ItemInventoryLogic itemLogic = controller.getItemLogic(type, lockedInventory);
+            return itemLogic.getDisplayName();
+        }
+        if (modeSelected(FLUID_IN, FLUID_OUT)) {
+            InventoryType type = modeSelected(FLUID_IN) ? InventoryType.Input : InventoryType.Output;
+            FluidInventoryLogic fluidLogic = controller.getFluidLogic(type, lockedInventory);
+            return fluidLogic.getDisplayName();
+        }
+        return "";
+    }
+
+    @Override
+    @Nonnull
+    public ForgeDirection getPowerOutputSide() {
+        if (!modeSelected(ENERGY_OUT)) return ForgeDirection.UNKNOWN;
+        return facing;
+    }
+
+    @Nonnull
+    protected GUIProvider<?> createGUIProvider() {
+        return new PartGUIProvider<>(this);
+    }
+
+    @Override
+    @Nonnull
+    public GUIProvider<?> getGUI(@Nonnull UIBuildContext uiContext) {
+        IMultiBlockController controller = getTarget(false);
+        if (controller == null) return guiProvider;
+        if (!modeSelected(NOTHING, ENERGY_IN, ENERGY_OUT)) return guiProvider;
+        if (!canOpenControllerGui()) return guiProvider;
+        if (uiContext.getPlayer()
+            .isSneaking()) return guiProvider;
+        GUIProvider<?> controllerGUI = controller.getGUI(uiContext);
+        return controllerGUI;
+    }
+
+    @Override
+    public ItemStack getAsItem() {
+        return MultiTileEntityRegistry.getRegistry(getMultiTileEntityRegistryID())
+            .getItem(getMultiTileEntityID());
+    }
+
+    @Override
+    public String getMachineName() {
+        return StatCollector.translateToLocal(getAsItem().getUnlocalizedName());
+    }
+
 }
-- 
cgit