aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/api/multitileentity
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/api/multitileentity')
-rw-r--r--src/main/java/gregtech/api/multitileentity/MultiTileEntityBlock.java654
-rw-r--r--src/main/java/gregtech/api/multitileentity/MultiTileEntityBlockInternal.java118
-rw-r--r--src/main/java/gregtech/api/multitileentity/MultiTileEntityClassContainer.java205
-rw-r--r--src/main/java/gregtech/api/multitileentity/MultiTileEntityContainer.java30
-rw-r--r--src/main/java/gregtech/api/multitileentity/MultiTileEntityItemInternal.java354
-rw-r--r--src/main/java/gregtech/api/multitileentity/MultiTileEntityRegistry.java290
-rw-r--r--src/main/java/gregtech/api/multitileentity/base/MultiTileEntity.java1381
-rw-r--r--src/main/java/gregtech/api/multitileentity/base/NonTickableMultiTileEntity.java62
-rw-r--r--src/main/java/gregtech/api/multitileentity/base/TickableMultiTileEntity.java173
-rw-r--r--src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileCasing.java43
-rw-r--r--src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileComponentCasing.java130
-rw-r--r--src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileMachine.java19
-rw-r--r--src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileUpgradeCasing.java71
-rw-r--r--src/main/java/gregtech/api/multitileentity/enums/MultiTileCasingPurpose.java13
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IItemUpdatable.java19
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockController.java51
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockEnergy.java42
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockFluidHandler.java32
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockInventory.java60
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockPart.java26
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiTileEntity.java293
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiTileMachine.java10
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/SyncedMultiTileEntity.java63
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/UpgradableModularMuTE.java10
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/UpgradableMuTE.java12
-rw-r--r--src/main/java/gregtech/api/multitileentity/machine/MultiTileBasicMachine.java800
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java111
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java1083
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java711
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/StackableController.java128
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/StackableModularController.java77
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/WallShareablePart.java96
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/BasicCasing.java7
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/CasingBehaviorBase.java10
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/FunctionalCasing.java29
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/Glasses.java32
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/UpgradeCasing.java35
37 files changed, 7280 insertions, 0 deletions
diff --git a/src/main/java/gregtech/api/multitileentity/MultiTileEntityBlock.java b/src/main/java/gregtech/api/multitileentity/MultiTileEntityBlock.java
new file mode 100644
index 0000000000..6ee760553e
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityBlock.java
@@ -0,0 +1,654 @@
+package gregtech.api.multitileentity;
+
+import static gregtech.api.enums.GT_Values.OFFX;
+import static gregtech.api.enums.GT_Values.OFFY;
+import static gregtech.api.enums.GT_Values.OFFZ;
+import static gregtech.api.util.GT_Util.LAST_BROKEN_TILEENTITY;
+import static gregtech.api.util.GT_Util.getTileEntity;
+import static gregtech.api.util.GT_Util.setTileEntity;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.ITileEntityProvider;
+import net.minecraft.block.material.Material;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.enchantment.EnchantmentHelper;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EnumCreatureType;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.init.Blocks;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.stats.StatList;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.IIcon;
+import net.minecraft.util.MovingObjectPosition;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.Explosion;
+import net.minecraft.world.IBlockAccess;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.event.ForgeEventFactory;
+
+import com.cricketcraft.chisel.api.IFacade;
+
+import cpw.mods.fml.common.Optional;
+import cpw.mods.fml.common.registry.GameRegistry;
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.ItemList;
+import gregtech.api.enums.Textures;
+import gregtech.api.interfaces.IDebugableBlock;
+import gregtech.api.interfaces.tileentity.IDebugableTileEntity;
+import gregtech.api.metatileentity.BaseTileEntity;
+import gregtech.api.metatileentity.CoverableTileEntity;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_BreakBlock;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_GetBlockHardness;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_GetComparatorInputOverride;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_GetWeakChanges;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_HasMultiBlockMachineRelevantData;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_IsProvidingStrongPower;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_IsProvidingWeakPower;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_OnNeighborBlockChange;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_ShouldCheckWeakPower;
+import gregtech.api.objects.XSTR;
+import gregtech.api.util.GT_Log;
+import gregtech.api.util.GT_Util;
+import gregtech.common.covers.CoverInfo;
+import gregtech.common.render.GT_MultiTile_Renderer;
+
+/*
+ * MultiTileEntityBlock ported from GT6
+ */
+@Optional.Interface(iface = "com.cricketcraft.chisel.api.IFacade", modid = "ChiselAPI")
+public class MultiTileEntityBlock extends Block implements IDebugableBlock, ITileEntityProvider, IFacade {
+
+ protected static final Map<String, MultiTileEntityBlock> MULTI_BLOCK_MAP = new HashMap<>();
+ private static boolean LOCK = false;
+
+ protected final String mNameInternal, mTool;
+ protected final int mHarvestLevelOffset, mHarvestLevelMinimum, mHarvestLevelMaximum;
+ protected final boolean mOpaque, mNormalCube;
+
+ public static String getName(String aMaterialName, SoundType aSoundType, String aTool, int aHarvestLevelOffset,
+ int aHarvestLevelMinimum, int aHarvestLevelMaximum, boolean aOpaque, boolean aNormalCube) {
+ return "gt.block.multiblock." + aMaterialName
+ + "."
+ + aSoundType.soundName
+ + "."
+ + aTool
+ + "."
+ + aHarvestLevelOffset
+ + "."
+ + aHarvestLevelMinimum
+ + "."
+ + aHarvestLevelMaximum
+ + "."
+ + aOpaque
+ + "."
+ + aNormalCube;
+ }
+
+ /**
+ * @param aMaterialName the Name of the vanilla Material Field. In case this is not a vanilla Material,
+ * insert the Name you want to give your own Material instead.
+ * @param aMaterial the Material used to determine the Block.
+ * @param aSoundType the Sound Type of the Block.
+ * @param aTool the Tool used to harvest this Block.
+ * @param aHarvestLevelOffset obvious
+ * @param aHarvestLevelMinimum obvious
+ * @param aHarvestLevelMaximum obvious
+ * @param aOpaque if this Block is Opaque.
+ * @param aNormalCube if this Block is a normal Cube (for Redstone Stuff).
+ */
+ public static MultiTileEntityBlock getOrCreate(String aModID, String aMaterialName, Material aMaterial,
+ SoundType aSoundType, String aTool, int aHarvestLevelOffset, int aHarvestLevelMinimum, int aHarvestLevelMaximum,
+ boolean aOpaque, boolean aNormalCube) {
+ final MultiTileEntityBlock rBlock = MULTI_BLOCK_MAP.get(
+ aModID + ":"
+ + getName(
+ aMaterialName,
+ aSoundType,
+ aTool = aTool.toLowerCase(),
+ aHarvestLevelOffset,
+ aHarvestLevelMinimum,
+ aHarvestLevelMaximum,
+ aOpaque,
+ aNormalCube));
+ return rBlock == null
+ ? new MultiTileEntityBlock(
+ aModID,
+ aMaterialName,
+ aMaterial,
+ aSoundType,
+ aTool,
+ aHarvestLevelOffset,
+ aHarvestLevelMinimum,
+ aHarvestLevelMaximum,
+ aOpaque,
+ aNormalCube)
+ : rBlock;
+ }
+
+ protected MultiTileEntityBlock(String aModID, String aMaterialName, Material aMaterial, SoundType aSoundType,
+ String aTool, int aHarvestLevelOffset, int aHarvestLevelMinimum, int aHarvestLevelMaximum, boolean aOpaque,
+ boolean aNormalCube) {
+ super(aMaterial);
+ if (GregTech_API.sPreloadFinished)
+ throw new IllegalStateException("Blocks can only be initialized within preInit!");
+
+ mNameInternal = getName(
+ aMaterialName,
+ aSoundType,
+ aTool,
+ aHarvestLevelOffset,
+ aHarvestLevelMinimum,
+ aHarvestLevelMaximum,
+ aOpaque,
+ aNormalCube);
+ GameRegistry.registerBlock(this, ItemBlock.class, mNameInternal);
+
+ MULTI_BLOCK_MAP.put(aModID + ":" + mNameInternal, this);
+
+ setStepSound(aSoundType);
+ mOpaque = aOpaque;
+ mNormalCube = aNormalCube;
+
+ mTool = aTool.toLowerCase();
+ mHarvestLevelOffset = aHarvestLevelOffset;
+ mHarvestLevelMinimum = Math.max(0, aHarvestLevelMinimum);
+ mHarvestLevelMaximum = Math.max(aHarvestLevelMinimum, aHarvestLevelMaximum);
+
+ opaque = isOpaqueCube();
+ lightOpacity = isOpaqueCube() ? 255 : 0;
+ }
+
+ @Override
+ public final void breakBlock(World aWorld, int aX, int aY, int aZ, Block aBlock, int aMetaData) {
+ final TileEntity aTileEntity = getTileEntity(aWorld, aX, aY, aZ, true);
+ if (aTileEntity != null) LAST_BROKEN_TILEENTITY.set(aTileEntity);
+ if (aTileEntity == null || !aTileEntity.shouldRefresh(this, aBlock, aMetaData, aMetaData, aWorld, aX, aY, aZ))
+ return;
+ if (aTileEntity instanceof IMTE_BreakBlock && ((IMTE_BreakBlock) aTileEntity).breakBlock()) return;
+ if (aTileEntity instanceof IMTE_HasMultiBlockMachineRelevantData
+ && ((IMTE_HasMultiBlockMachineRelevantData) aTileEntity).hasMultiBlockMachineRelevantData())
+ GregTech_API.causeMachineUpdate(aWorld, aX, aY, aZ);
+
+ aWorld.removeTileEntity(aX, aY, aZ);
+ }
+
+ @Override
+ public ArrayList<String> getDebugInfo(EntityPlayer aPlayer, int aX, int aY, int aZ, int aLogLevel) {
+ final TileEntity aTileEntity = aPlayer.worldObj.getTileEntity(aX, aY, aZ);
+ if (aTileEntity instanceof IDebugableTileEntity mte) {
+ return mte.getDebugInfo(aPlayer, aLogLevel);
+ }
+ return new ArrayList<>();
+ }
+
+ @Override
+ public final boolean func_149730_j /* isFullBlock */() {
+ return mOpaque;
+ }
+
+ @Override
+ public final boolean isNormalCube() {
+ return mNormalCube;
+ }
+
+ @Override
+ public final boolean renderAsNormalBlock() {
+ return mOpaque || mNormalCube;
+ }
+
+ @Override
+ public int getRenderType() {
+ return GT_MultiTile_Renderer.INSTANCE == null ? super.getRenderType()
+ : GT_MultiTile_Renderer.INSTANCE.getRenderId();
+ }
+
+ @Override
+ public final float getBlockHardness(World aWorld, int aX, int aY, int aZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMTE_GetBlockHardness ? ((IMTE_GetBlockHardness) aTileEntity).getBlockHardness()
+ : 1.0F;
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ public IIcon getIcon(IBlockAccess aIBlockAccess, int aX, int aY, int aZ, int ordinalSide) {
+ return Textures.BlockIcons.MACHINE_LV_SIDE.getIcon();
+ }
+
+ @SideOnly(Side.CLIENT)
+ @Override
+ public IIcon getIcon(int ordinalSide, int aMeta) {
+ return Textures.BlockIcons.MACHINE_LV_SIDE.getIcon();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public final void addCollisionBoxesToList(World aWorld, int aX, int aY, int aZ, AxisAlignedBB aAABB,
+ List<AxisAlignedBB> aList, Entity aEntity) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (aTileEntity instanceof IMultiTileEntity)
+ ((IMultiTileEntity) aTileEntity).addCollisionBoxesToList(aAABB, aList, aEntity);
+ else super.addCollisionBoxesToList(aWorld, aX, aY, aZ, aAABB, aList, aEntity);
+ }
+
+ @Override
+ public final AxisAlignedBB getCollisionBoundingBoxFromPool(World aWorld, int aX, int aY, int aZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMultiTileEntity mte ? mte.getCollisionBoundingBoxFromPool()
+ : aTileEntity == null ? null : super.getCollisionBoundingBoxFromPool(aWorld, aX, aY, aZ);
+ }
+
+ @Override
+ public final AxisAlignedBB getSelectedBoundingBoxFromPool(World aWorld, int aX, int aY, int aZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMultiTileEntity mte ? mte.getSelectedBoundingBoxFromPool()
+ : super.getSelectedBoundingBoxFromPool(aWorld, aX, aY, aZ);
+ }
+
+ @Override
+ public void setBlockBoundsBasedOnState(IBlockAccess blockAccess, int aX, int aY, int aZ) {
+ final TileEntity aTileEntity = blockAccess.getTileEntity(aX, aY, aZ);
+ if (aTileEntity instanceof IMultiTileEntity mte) {
+ mte.setBlockBoundsBasedOnState(this);
+ return;
+ }
+ super.setBlockBoundsBasedOnState(blockAccess, aX, aY, aZ);
+ }
+
+ @Override
+ public final boolean isOpaqueCube() {
+ return mOpaque;
+ }
+
+ @Override
+ public final void onNeighborChange(IBlockAccess aWorld, int aX, int aY, int aZ, int aTileX, int aTileY,
+ int aTileZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (!LOCK) {
+ LOCK = true;
+ if (aTileEntity instanceof BaseTileEntity)
+ ((BaseTileEntity) aTileEntity).onAdjacentBlockChange(aTileX, aTileY, aTileZ);
+ LOCK = false;
+ }
+ }
+
+ @Override
+ public void onNeighborBlockChange(World aWorld, int aX, int aY, int aZ, Block aBlock) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (!LOCK) {
+ LOCK = true;
+ if (aTileEntity instanceof BaseTileEntity bte) bte.onAdjacentBlockChange(aX, aY, aZ);
+ LOCK = false;
+ }
+ if (aTileEntity instanceof IMTE_OnNeighborBlockChange change) change.onNeighborBlockChange(aWorld, aBlock);
+ if (aTileEntity == null) aWorld.setBlockToAir(aX, aY, aZ);
+ }
+
+ @Override
+ public final void onBlockAdded(World aWorld, int aX, int aY, int aZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (aTileEntity instanceof IMultiTileEntity mte) mte.onBlockAdded();
+ }
+
+ @Override
+ public float getPlayerRelativeBlockHardness(EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMultiTileEntity mte && mte.privateAccess()
+ && !((IMultiTileEntity) aTileEntity).playerOwnsThis(aPlayer, true) ? -1.0F
+ : super.getPlayerRelativeBlockHardness(aPlayer, aWorld, aX, aY, aZ);
+ }
+
+ @Override
+ public final void onBlockClicked(World aWorld, int aX, int aY, int aZ, EntityPlayer aPlayer) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (aTileEntity instanceof IMultiTileEntity mte) mte.onLeftClick(aPlayer);
+ else super.onBlockClicked(aWorld, aX, aY, aZ, aPlayer);
+ }
+
+ @Override
+ public boolean onBlockActivated(World aWorld, int aX, int aY, int aZ, EntityPlayer aPlayer, int ordinalSide,
+ float aHitX, float aHitY, float aHitZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (aPlayer != null && ItemList.TC_Thaumometer.isStackEqual(aPlayer.getHeldItem(), true, true)) return false;
+ return aTileEntity instanceof IMultiTileEntity mte
+ && mte.onBlockActivated(aPlayer, ForgeDirection.getOrientation(ordinalSide), aHitX, aHitY, aHitZ);
+ }
+
+ @Override
+ public final int isProvidingWeakPower(IBlockAccess aWorld, int aX, int aY, int aZ, int ordinalSide) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMTE_IsProvidingWeakPower power
+ ? power.isProvidingWeakPower(ForgeDirection.getOrientation(ordinalSide))
+ : super.isProvidingWeakPower(aWorld, aX, aY, aZ, ordinalSide);
+ }
+
+ @Override
+ public final int isProvidingStrongPower(IBlockAccess aWorld, int aX, int aY, int aZ, int ordinalSide) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMTE_IsProvidingStrongPower power
+ ? power.isProvidingStrongPower(ForgeDirection.getOrientation(ordinalSide))
+ : super.isProvidingStrongPower(aWorld, aX, aY, aZ, ordinalSide);
+ }
+
+ @Override
+ public final boolean shouldCheckWeakPower(IBlockAccess aWorld, int aX, int aY, int aZ, int ordinalSide) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMTE_ShouldCheckWeakPower power
+ ? power.shouldCheckWeakPower(ForgeDirection.getOrientation(ordinalSide))
+ : isNormalCube(aWorld, aX, aY, aZ);
+ }
+
+ @Override
+ public final boolean getWeakChanges(IBlockAccess aWorld, int aX, int aY, int aZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMTE_GetWeakChanges changes ? changes.getWeakChanges()
+ : super.getWeakChanges(aWorld, aX, aY, aZ);
+ }
+
+ @Override
+ public final void harvestBlock(World aWorld, EntityPlayer aPlayer, int aX, int aY, int aZ, int aMeta) {
+ if (aPlayer == null) aPlayer = harvesters.get();
+ aPlayer.addStat(StatList.mineBlockStatArray[getIdFromBlock(this)], 1);
+ aPlayer.addExhaustion(0.025F);
+ final boolean aSilkTouch = EnchantmentHelper.getSilkTouchModifier(aPlayer);
+ final int aFortune = EnchantmentHelper.getFortuneModifier(aPlayer);
+ float aChance = 1.0F;
+ final TileEntity aTileEntity = getTileEntity(aWorld, aX, aY, aZ, true);
+
+ if (!(aTileEntity instanceof IMultiTileEntity mte)) {
+ return;
+ }
+
+ final ArrayList<ItemStack> tList = mte.getDrops(aFortune, aSilkTouch);
+ aChance = ForgeEventFactory
+ .fireBlockHarvesting(tList, aWorld, this, aX, aY, aZ, aMeta, aFortune, aChance, aSilkTouch, aPlayer);
+ for (final ItemStack tStack : tList)
+ if (XSTR.XSTR_INSTANCE.nextFloat() <= aChance) dropBlockAsItem(aWorld, aX, aY, aZ, tStack);
+
+ }
+
+ @Override
+ public final boolean shouldSideBeRendered(IBlockAccess aWorld, int aX, int aY, int aZ, int ordinalSide) {
+ final TileEntity aTileEntity = aWorld
+ .getTileEntity(aX - OFFX[ordinalSide], aY - OFFY[ordinalSide], aZ - OFFZ[ordinalSide]);
+ return aTileEntity instanceof IMultiTileEntity mte
+ ? mte.shouldSideBeRendered(ForgeDirection.getOrientation(ordinalSide))
+ : super.shouldSideBeRendered(aWorld, aX, aY, aZ, ordinalSide);
+ }
+
+ @Override
+ public Block getFacade(IBlockAccess aWorld, int aX, int aY, int aZ, int ordinalSide) {
+ final TileEntity tTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (tTileEntity instanceof CoverableTileEntity tile) {
+ final ForgeDirection side = ForgeDirection.getOrientation(ordinalSide);
+ if (ordinalSide != -1) {
+ final Block facadeBlock = tile.getCoverInfoAtSide(side)
+ .getFacadeBlock();
+ if (facadeBlock != null) return facadeBlock;
+ } else {
+ // we do not allow more than one type of facade per block, so no need to check every side
+ // see comment in gregtech.common.covers.GT_Cover_FacadeBase.isCoverPlaceable
+ for (final ForgeDirection tSide : ForgeDirection.VALID_DIRECTIONS) {
+ final Block facadeBlock = tile.getCoverInfoAtSide(tSide)
+ .getFacadeBlock();
+ if (facadeBlock != null) {
+ return facadeBlock;
+ }
+ }
+ }
+ }
+ return Blocks.air;
+ }
+
+ @Override
+ public int getFacadeMetadata(IBlockAccess aWorld, int aX, int aY, int aZ, int ordinalSide) {
+ final TileEntity tTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (tTileEntity instanceof CoverableTileEntity tile) {
+ final ForgeDirection side = ForgeDirection.getOrientation(ordinalSide);
+ if (ordinalSide != -1) {
+ final CoverInfo coverInfo = tile.getCoverInfoAtSide(side);
+ final Block facadeBlock = coverInfo.getFacadeBlock();
+ if (facadeBlock != null) return coverInfo.getFacadeMeta();
+ } else {
+ // we do not allow more than one type of facade per block, so no need to check every side
+ // see comment in gregtech.common.covers.GT_Cover_FacadeBase.isCoverPlaceable
+ for (final ForgeDirection tSide : ForgeDirection.VALID_DIRECTIONS) {
+ final CoverInfo coverInfo = tile.getCoverInfoAtSide(tSide);
+ final Block facadeBlock = coverInfo.getFacadeBlock();
+ if (facadeBlock != null) {
+ return coverInfo.getFacadeMeta();
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ protected boolean canSilkHarvest() {
+ return false;
+ }
+
+ @Override
+ public final boolean canProvidePower() {
+ return !mNormalCube;
+ }
+
+ @Override
+ public final String getLocalizedName() {
+ return StatCollector.translateToLocal(mNameInternal + ".name");
+ }
+
+ @Override
+ public final String getUnlocalizedName() {
+ return mNameInternal;
+ }
+
+ @Override
+ public final boolean onBlockEventReceived(World aWorld, int aX, int aY, int aZ, int aID, int aData) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity == null || aTileEntity.receiveClientEvent(aID, aData);
+ }
+
+ @Override
+ public final void getSubBlocks(Item aItem, CreativeTabs aCreativeTab, List<ItemStack> aList) {
+ /**/
+ }
+
+ @Override
+ public boolean hasComparatorInputOverride() {
+ return true;
+ }
+
+ @Override
+ public final int getComparatorInputOverride(World aWorld, int aX, int aY, int aZ, int ordinalSide) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ if (aTileEntity instanceof IMTE_GetComparatorInputOverride override) {
+ return override.getComparatorInputOverride(ForgeDirection.getOrientation(ordinalSide));
+ }
+
+ if (aTileEntity instanceof IMTE_IsProvidingWeakPower power) {
+ return power.isProvidingWeakPower(
+ ForgeDirection.getOrientation(ordinalSide)
+ .getOpposite());
+ }
+
+ return super.getComparatorInputOverride(aWorld, aX, aY, aZ, ordinalSide);
+ }
+
+ @Override
+ public final void registerBlockIcons(IIconRegister aIconRegister) {
+ /**/
+ }
+
+ @Override
+ public final boolean isNormalCube(IBlockAccess aWorld, int aX, int aY, int aZ) {
+ return mNormalCube;
+ }
+
+ @Override
+ public final boolean isSideSolid(IBlockAccess aWorld, int aX, int aY, int aZ, ForgeDirection side) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMultiTileEntity mte ? mte.isSideSolid(side) : mOpaque;
+ }
+
+ @Override
+ public boolean removedByPlayer(World aWorld, EntityPlayer aPlayer, int aX, int aY, int aZ, boolean aWillHarvest) {
+ final TileEntity aTileEntity = GT_Util.getTileEntity(aWorld, aX, aY, aZ, true);
+ if (aTileEntity != null) LAST_BROKEN_TILEENTITY.set(aTileEntity);
+ return super.removedByPlayer(aWorld, aPlayer, aX, aY, aZ, aWillHarvest);
+ }
+
+ @Override
+ public int getFlammability(IBlockAccess aWorld, int aX, int aY, int aZ, ForgeDirection face) {
+ return 0;
+ }
+
+ @Override
+ public int getFireSpreadSpeed(IBlockAccess aWorld, int aX, int aY, int aZ, ForgeDirection face) {
+ return GregTech_API.sMachineFlammable && (aWorld.getBlockMetadata(aX, aY, aZ) == 0) ? 100 : 0;
+ }
+
+ @Override
+ public boolean hasTileEntity(int aMeta) {
+ return true;
+ }
+
+ @Override
+ public final ArrayList<ItemStack> getDrops(World aWorld, int aX, int aY, int aZ, int aUnusableMetaData,
+ int aFortune) {
+ final TileEntity aTileEntity = getTileEntity(aWorld, aX, aY, aZ, true);
+ if (aTileEntity instanceof IMultiTileEntity mte) return mte.getDrops(aFortune, false);
+ return new ArrayList<>();
+ }
+
+ @Override
+ public boolean canCreatureSpawn(EnumCreatureType type, IBlockAccess aWorld, int aX, int aY, int aZ) {
+ return false;
+ }
+
+ @Override
+ public final float getExplosionResistance(Entity aExploder, World aWorld, int aX, int aY, int aZ,
+ double aExplosionX, double aExplosionY, double aExplosionZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMultiTileEntity mte
+ ? mte.getExplosionResistance(aExploder, aExplosionX, aExplosionY, aExplosionZ)
+ : 1.0F;
+ }
+
+ @Override
+ public final void onBlockExploded(World aWorld, int aX, int aY, int aZ, Explosion aExplosion) {
+ if (aWorld.isRemote) return;
+ final TileEntity aTileEntity = getTileEntity(aWorld, aX, aY, aZ, true);
+ if (aTileEntity != null) LAST_BROKEN_TILEENTITY.set(aTileEntity);
+ if (aTileEntity instanceof IMultiTileEntity mte) {
+ GT_Log.exp.printf(
+ "Explosion at : %d | %d | %d DIMID: %s due to near explosion!%n",
+ aX,
+ aY,
+ aZ,
+ aWorld.provider.dimensionId);
+ mte.onExploded(aExplosion);
+ } else aWorld.setBlockToAir(aX, aY, aZ);
+ }
+
+ @Override
+ public final boolean canConnectRedstone(IBlockAccess aWorld, int aX, int aY, int aZ, int ordinalSide) {
+ return true;
+ }
+
+ @Override
+ public final boolean recolourBlock(World aWorld, int aX, int aY, int aZ, ForgeDirection side, int aColor) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMultiTileEntity mte && mte.recolourBlock(side, (byte) aColor);
+ }
+
+ @Override
+ public final String getHarvestTool(int aMeta) {
+ return mTool;
+ }
+
+ @Override
+ public final int getHarvestLevel(int aMeta) {
+ return Math.max(mHarvestLevelMinimum, Math.min(mHarvestLevelMaximum, mHarvestLevelOffset + aMeta));
+ }
+
+ @Override
+ public final boolean isToolEffective(String aType, int aMeta) {
+ return getHarvestTool(aMeta).equals(aType);
+ }
+
+ @Override
+ public final ItemStack getPickBlock(MovingObjectPosition aTarget, World aWorld, int aX, int aY, int aZ,
+ EntityPlayer aPlayer) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMultiTileEntity mte ? mte.getPickBlock(aTarget) : null;
+ }
+
+ @Override
+ public final ItemStack getPickBlock(MovingObjectPosition aTarget, World aWorld, int aX, int aY, int aZ) {
+ final TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+ return aTileEntity instanceof IMultiTileEntity mte ? mte.getPickBlock(aTarget) : null;
+ }
+
+ @Nullable
+ public final IMultiTileEntity receiveMultiTileEntityData(@Nonnull IBlockAccess aWorld, int aX, int aY, int aZ,
+ int registryId, int aID) {
+ if (!(aWorld instanceof World)) return null;
+ TileEntity aTileEntity = aWorld.getTileEntity(aX, aY, aZ);
+
+ if (!(aTileEntity instanceof IMultiTileEntity mte) || mte.getMultiTileEntityRegistryID() != registryId
+ || mte.getMultiTileEntityID() != aID) {
+ final MultiTileEntityRegistry tRegistry = MultiTileEntityRegistry.getRegistry(registryId);
+ if (tRegistry == null) return null;
+
+ aTileEntity = tRegistry.getNewTileEntity((World) aWorld, aX, aY, aZ, aID);
+ if (!(aTileEntity instanceof IMultiTileEntity)) return null;
+
+ setTileEntity((World) aWorld, aX, aY, aZ, aTileEntity, false);
+ }
+ return (IMultiTileEntity) aTileEntity;
+ }
+
+ public void receiveCoverData(IMultiTileEntity mte, int aCover0, int aCover1, int aCover2, int aCover3, int aCover4,
+ int aCover5) {
+ boolean updated;
+ updated = mte.setCoverIDAtSideNoUpdate(ForgeDirection.DOWN, aCover0);
+ updated |= mte.setCoverIDAtSideNoUpdate(ForgeDirection.UP, aCover1);
+ updated |= mte.setCoverIDAtSideNoUpdate(ForgeDirection.NORTH, aCover2);
+ updated |= mte.setCoverIDAtSideNoUpdate(ForgeDirection.SOUTH, aCover3);
+ updated |= mte.setCoverIDAtSideNoUpdate(ForgeDirection.WEST, aCover4);
+ updated |= mte.setCoverIDAtSideNoUpdate(ForgeDirection.EAST, aCover5);
+
+ if (updated) {
+ mte.issueBlockUpdate();
+ }
+ }
+
+ @Override
+ public final TileEntity createTileEntity(World aWorld, int aMeta) {
+ return null;
+ }
+
+ @Override
+ public TileEntity createNewTileEntity(World world, int i) {
+ return null;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/MultiTileEntityBlockInternal.java b/src/main/java/gregtech/api/multitileentity/MultiTileEntityBlockInternal.java
new file mode 100644
index 0000000000..17e217ae7e
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityBlockInternal.java
@@ -0,0 +1,118 @@
+package gregtech.api.multitileentity;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+import static gregtech.api.util.GT_Util.setTileEntity;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.material.Material;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.init.Blocks;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import gregtech.api.GregTech_API;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_HasMultiBlockMachineRelevantData;
+import gregtech.common.render.GT_MultiTile_Renderer;
+
+public class MultiTileEntityBlockInternal extends Block {
+
+ public MultiTileEntityRegistry mMultiTileEntityRegistry;
+
+ public MultiTileEntityBlockInternal() {
+ super(Material.anvil);
+ }
+
+ @Override
+ public void registerBlockIcons(IIconRegister aIconRegister) {
+ /* Do Nothing */
+ }
+
+ @Override
+ public int getRenderType() {
+ return GT_MultiTile_Renderer.INSTANCE == null ? super.getRenderType()
+ : GT_MultiTile_Renderer.INSTANCE.getRenderId();
+ }
+
+ @Override
+ public final String getUnlocalizedName() {
+ return mMultiTileEntityRegistry.mNameInternal;
+ }
+
+ @Override
+ public final String getLocalizedName() {
+ return StatCollector.translateToLocal(mMultiTileEntityRegistry.mNameInternal + ".name");
+ }
+
+ public boolean placeBlock(World aWorld, int aX, int aY, int aZ, ForgeDirection side, short aMetaData,
+ NBTTagCompound aNBT, boolean aCauseBlockUpdates, boolean aForcePlacement) {
+ final MultiTileEntityContainer aMTEContainer = mMultiTileEntityRegistry
+ .getNewTileEntityContainer(aWorld, aX, aY, aZ, aMetaData, aNBT);
+ if (aMTEContainer == null) return false;
+
+ final Block tReplacedBlock = aWorld.getBlock(aX, aY, aZ);
+
+ // This is some complicated bullshit Greg had to do to make his MTEs work right.
+ // Set Block with reverse MetaData first.
+ aWorld.setBlock(aX, aY, aZ, aMTEContainer.mBlock, 15 - aMTEContainer.mBlockMetaData, 2);
+ // Make sure the Block has been set, yes I know setBlock has a true/false return value, but guess what, it is
+ // not reliable in 0.0001% of cases! -Greg
+ if (aWorld.getBlock(aX, aY, aZ) != aMTEContainer.mBlock) {
+ aWorld.setBlock(aX, aY, aZ, Blocks.air, 0, 0);
+ return false;
+ }
+ // TileEntity should not refresh yet! -Greg
+ ((IMultiTileEntity) aMTEContainer.mTileEntity).setShouldRefresh(false);
+ // Fake-Set the TileEntity first, bypassing a lot of checks. -Greg
+ setTileEntity(aWorld, aX, aY, aZ, aMTEContainer.mTileEntity, false);
+ // Now set the Block with the REAL MetaData. -Greg
+ setTileEntity(aWorld, aX, aY, aZ, aMTEContainer.mBlock, aMTEContainer.mBlockMetaData, 0, false);
+ // When the TileEntity is set now it SHOULD refresh! -Greg
+ ((IMultiTileEntity) aMTEContainer.mTileEntity).setShouldRefresh(true);
+ // But make sure again that the Block we have set was actually set properly, because 0.0001%! -Greg
+ if (aWorld.getBlock(aX, aY, aZ) != aMTEContainer.mBlock) {
+ aWorld.setBlock(aX, aY, aZ, Blocks.air, 0, 0);
+ return false;
+ }
+ // And finally properly set the TileEntity for real! -Greg
+ setTileEntity(aWorld, aX, aY, aZ, aMTEContainer.mTileEntity, aCauseBlockUpdates);
+ // Yep, all this just to set one Block and its TileEntity properly... -Greg
+
+ try {
+ if (aMTEContainer.mTileEntity instanceof IMTE_HasMultiBlockMachineRelevantData) {
+ if (((IMTE_HasMultiBlockMachineRelevantData) aMTEContainer.mTileEntity)
+ .hasMultiBlockMachineRelevantData()) GregTech_API.causeMachineUpdate(aWorld, aX, aY, aZ);
+ }
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("causeMachineUpdate", e);
+ }
+
+ try {
+ if (!aWorld.isRemote && aCauseBlockUpdates) {
+ aWorld.notifyBlockChange(aX, aY, aZ, tReplacedBlock);
+ aWorld.func_147453_f(aX, aY, aZ, aMTEContainer.mBlock);
+ }
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("aCauseBlockUpdates", e);
+ }
+
+ try {
+ ((IMultiTileEntity) aMTEContainer.mTileEntity).onTileEntityPlaced();
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("onTileEntityPlaced", e);
+ }
+
+ try {
+ aWorld.func_147451_t /* updateAllLightTypes */(aX, aY, aZ);
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("updateAllLightTypes", e);
+ }
+ return true;
+ }
+
+ public MultiTileEntityRegistry getRegistry() {
+ return mMultiTileEntityRegistry;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/MultiTileEntityClassContainer.java b/src/main/java/gregtech/api/multitileentity/MultiTileEntityClassContainer.java
new file mode 100644
index 0000000000..4ce4c3c886
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityClassContainer.java
@@ -0,0 +1,205 @@
+package gregtech.api.multitileentity;
+
+import static gregtech.api.enums.GT_Values.NBT;
+
+import java.lang.ref.WeakReference;
+
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.Tuple;
+
+import gregtech.api.enums.Materials;
+import gregtech.api.multitileentity.base.MultiTileEntity;
+import gregtech.api.multitileentity.multiblock.casing.FunctionalCasing;
+import gregtech.api.multitileentity.multiblock.casing.UpgradeCasing;
+import gregtech.api.util.GT_Util;
+import gregtech.common.tileentities.casings.upgrade.Inventory;
+import gregtech.common.tileentities.casings.upgrade.Tank;
+
+public class MultiTileEntityClassContainer {
+
+ private final WeakReference<MultiTileEntityRegistry> mRegistry;
+ private String mLocalized;
+ private String mCategoryName;
+
+ public final short mID;
+ public Class<? extends MultiTileEntity> mClass;
+ public MultiTileEntityBlock mBlock;
+ public MultiTileEntity mCanonicalTileEntity;
+ public NBTTagCompound mParameters;
+
+ // These have defaults
+ public byte mBlockMetaData = 1;
+ public byte mStackSize = 64;
+ public boolean mHidden = false;
+
+ public MultiTileEntityClassContainer(MultiTileEntityRegistry aRegistry, int aID,
+ Class<? extends MultiTileEntity> aClass) {
+ /* Start the Builder */
+ mRegistry = new WeakReference<>(aRegistry);
+ mID = (short) aID;
+ mClass = aClass;
+ mParameters = new NBTTagCompound();
+ }
+
+ public boolean register() {
+ /* End and register the Builder with the registry */
+ final MultiTileEntityRegistry registry = mRegistry.get();
+
+ if (mParameters.hasKey(NBT.MATERIAL) && !mParameters.hasKey(NBT.COLOR)) mParameters.setInteger(
+ NBT.COLOR,
+ GT_Util.getRGBInt(
+ Materials.get(mParameters.getString(NBT.MATERIAL))
+ .getRGBA()));
+
+ try {
+ mCanonicalTileEntity = mClass.newInstance();
+ } catch (Throwable e) {
+ throw new IllegalArgumentException(e);
+ }
+ mCanonicalTileEntity.initFromNBT(mParameters, mID, (short) -1);
+
+ return registry != null && registry.add(this.mLocalized, this.mCategoryName, this) != null;
+ }
+
+ public MultiTileEntityClassContainer name(String aName) {
+ mLocalized = aName;
+ return this;
+ }
+
+ public MultiTileEntityClassContainer category(String aCategoryName) {
+ mCategoryName = aCategoryName;
+ return this;
+ }
+
+ public MultiTileEntityClassContainer meta(int aMeta) {
+ mBlockMetaData = (byte) aMeta;
+ return this;
+ }
+
+ public MultiTileEntityClassContainer stackSize(int aStackSize) {
+ mStackSize = (byte) aStackSize;
+ return this;
+ }
+
+ public MultiTileEntityClassContainer hide() {
+ mHidden = true;
+ return this;
+ }
+
+ public MultiTileEntityClassContainer setBlock(MultiTileEntityBlock aBlock) {
+ mBlock = aBlock;
+ return this;
+ }
+
+ /* These methods are builder methods for commonly used NBT tags */
+
+ // Need a base texture for the MTE machine, and then a separate texture set for the machine/active overlays
+
+ public MultiTileEntityClassContainer material(Materials material) {
+ // Sets the material, and the color from the material, if not already set
+ mParameters.setString(NBT.MATERIAL, material.toString());
+ if (!mParameters.hasKey(NBT.COLOR)) mParameters.setInteger(NBT.COLOR, GT_Util.getRGBInt(material.getRGBA()));
+ return this;
+ }
+
+ public MultiTileEntityClassContainer color(int rbg) {
+ mParameters.setInteger(NBT.COLOR, rbg);
+ return this;
+ }
+
+ public MultiTileEntityClassContainer color(short[] rgba) {
+ mParameters.setInteger(NBT.COLOR, GT_Util.getRGBInt(rgba));
+ return this;
+ }
+
+ public MultiTileEntityClassContainer textureFolder(String texture) {
+ mParameters.setString(NBT.TEXTURE_FOLDER, texture);
+ return this;
+ }
+
+ public MultiTileEntityClassContainer inputInventorySize(int aSize) {
+ mParameters.setInteger(NBT.INV_INPUT_SIZE, aSize);
+ return this;
+ }
+
+ public MultiTileEntityClassContainer outputInventorySize(int aSize) {
+ mParameters.setInteger(NBT.INV_OUTPUT_SIZE, aSize);
+ return this;
+ }
+
+ public MultiTileEntityClassContainer tankCapacity(Long aCapacity) {
+ mParameters.setLong(NBT.TANK_CAPACITY, aCapacity);
+ return this;
+ }
+
+ public MultiTileEntityClassContainer tier(int aTier) {
+ verifyDescendentOfMultiple(true, UpgradeCasing.class, FunctionalCasing.class);
+ mParameters.setInteger(NBT.TIER, aTier);
+ return this;
+ }
+
+ public MultiTileEntityClassContainer upgradeInventorySize(int aSize) {
+ verifyDescendentOf(Inventory.class);
+
+ mParameters.setInteger(NBT.UPGRADE_INVENTORY_SIZE, aSize);
+ return this;
+ }
+
+ public MultiTileEntityClassContainer upgradeTankCount(int count) {
+ verifyDescendentOf(Tank.class);
+
+ mParameters.setInteger(NBT.UPGRADE_TANK_COUNT, count);
+ return this;
+ }
+
+ public MultiTileEntityClassContainer upgradeTankCapacity(Long aCapacity) {
+ mParameters.setLong(NBT.UPGRADE_TANK_CAPACITY, aCapacity);
+ return this;
+ }
+
+ public MultiTileEntityClassContainer upgradeAmperage(long amperage) {
+ mParameters.setLong(NBT.UPGRADE_AMPERAGE, amperage);
+ return this;
+ }
+
+ @SuppressWarnings("unused")
+ public MultiTileEntityClassContainer setNBT(String key, Object val) {
+ return setNBT(new Tuple(key, val));
+ }
+
+ public MultiTileEntityClassContainer setNBT(Tuple... aTags) {
+ /*
+ * Merge in arbitrary NBT tuples of (key, value). Useful for anything for which a custom method has not yet been
+ * exposed
+ */
+ mParameters = GT_Util.fuseNBT(mParameters, GT_Util.makeNBT(aTags));
+ return this;
+ }
+
+ private void verifyDescendentOf(Class<?> cls) {
+ // Check if cls is extended by mClass
+ if (!cls.isAssignableFrom(mClass)) {
+ throw new IllegalArgumentException(
+ "Expected a descendent of " + cls.getName() + " got " + mClass.getName() + " instead.");
+ }
+ }
+
+ private void verifyDescendentOfMultiple(boolean onlyOne, Class<?>... classes) {
+ boolean atLeastOne = false;
+ String classNames = "";
+ for (Class<?> cls : classes) {
+ classNames += cls.getName() + " ";
+ if (!onlyOne) {
+ verifyDescendentOf(cls);
+ atLeastOne = true;
+ } else if (cls.isAssignableFrom(mClass)) {
+ atLeastOne = true;
+ }
+ }
+
+ if (!atLeastOne) {
+ throw new IllegalArgumentException(
+ "Expected a descendent of any of these " + classNames + " got " + mClass.getName() + " instead.");
+ }
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/MultiTileEntityContainer.java b/src/main/java/gregtech/api/multitileentity/MultiTileEntityContainer.java
new file mode 100644
index 0000000000..3510140c12
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityContainer.java
@@ -0,0 +1,30 @@
+package gregtech.api.multitileentity;
+
+import static gregtech.api.util.GT_Util.setTileEntity;
+
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.world.World;
+
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
+
+public class MultiTileEntityContainer {
+
+ public final TileEntity mTileEntity;
+ public final MultiTileEntityBlock mBlock;
+ public final byte mBlockMetaData;
+
+ public MultiTileEntityContainer(TileEntity aTileEntity, MultiTileEntityBlock aBlock, byte aBlockMetaData) {
+ mBlockMetaData = aBlockMetaData;
+ mTileEntity = aTileEntity;
+ mBlock = aBlock;
+ }
+
+ public void setMultiTile(World aWorld, int aX, int aY, int aZ) {
+ // This is some complicated Bullshit Greg had to do to make his MTEs work right.
+ ((IMultiTileEntity) mTileEntity).setShouldRefresh(false);
+ setTileEntity(aWorld, aX, aY, aZ, mTileEntity, false);
+ setTileEntity(aWorld, aX, aY, aZ, mBlock, mBlockMetaData, 0, false);
+ ((IMultiTileEntity) mTileEntity).setShouldRefresh(true);
+ setTileEntity(aWorld, aX, aY, aZ, mTileEntity, true);
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/MultiTileEntityItemInternal.java b/src/main/java/gregtech/api/multitileentity/MultiTileEntityItemInternal.java
new file mode 100644
index 0000000000..cc10485f84
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityItemInternal.java
@@ -0,0 +1,354 @@
+package gregtech.api.multitileentity;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+import static gregtech.api.enums.GT_Values.SIDE_TOP;
+
+import java.util.List;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockSnow;
+import net.minecraft.client.renderer.texture.IIconRegister;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.init.Blocks;
+import net.minecraft.init.Items;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.IFluidContainerItem;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import gregtech.api.GregTech_API;
+import gregtech.api.metatileentity.CoverableTileEntity;
+import gregtech.api.multitileentity.interfaces.IItemUpdatable;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_AddToolTips;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_CanPlace;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_GetMaxStackSize;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_HasMultiBlockMachineRelevantData;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_IgnoreEntityCollisionWhenPlacing;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_OnlyPlaceableWhenSneaking;
+
+public class MultiTileEntityItemInternal extends ItemBlock implements IFluidContainerItem, IItemUpdatable {
+
+ public final MultiTileEntityBlockInternal mBlock;
+
+ public MultiTileEntityItemInternal(Block aBlock) {
+ super(aBlock);
+ setMaxDamage(0);
+ setHasSubtypes(true);
+ mBlock = (MultiTileEntityBlockInternal) aBlock;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void addInformation(ItemStack aStack, EntityPlayer aPlayer, List<String> aList, boolean aF3_H) {
+ final MultiTileEntityContainer tTileEntityContainer = mBlock.mMultiTileEntityRegistry
+ .getCachedTileEntityContainer(aStack);
+ if (tTileEntityContainer == null) {
+ aList.add("INVALID ITEM!");
+ return;
+ }
+ if (tTileEntityContainer.mTileEntity instanceof IMTE_AddToolTips mte) {
+ try {
+ mte.addToolTips(aList, aStack, aF3_H);
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("addInformation", e);
+ }
+ }
+ final NBTTagCompound aNBT = aStack.getTagCompound();
+ CoverableTileEntity.addInstalledCoversInformation(aNBT, aList);
+ // TODO: Add anything else relevant
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ @SuppressWarnings("unchecked")
+ public void getSubItems(Item aItem, CreativeTabs aTab, List<ItemStack> aList) {
+ for (MultiTileEntityClassContainer tClass : mBlock.mMultiTileEntityRegistry.mRegistrations) {
+ if (!tClass.mHidden && ((IMultiTileEntity) tClass.mCanonicalTileEntity)
+ .getSubItems(mBlock, aItem, aTab, aList, tClass.mID)) {
+ aList.add(mBlock.mMultiTileEntityRegistry.getItem(tClass.mID));
+ }
+ }
+ }
+
+ @Override
+ public boolean onItemUse(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ,
+ int ordinalSide, float aHitX, float aHitY, float aHitZ) {
+
+ if (aY < 0 || aY > aWorld.getHeight()) return false;
+
+ if (aPlayer == null) return false;
+
+ try {
+ ForgeDirection side = ForgeDirection.getOrientation(ordinalSide);
+ final Block tClickedBlock = aWorld.getBlock(aX, aY, aZ);
+
+ if (tClickedBlock instanceof BlockSnow && (aWorld.getBlockMetadata(aX, aY, aZ) & 7) < 1) {
+ ordinalSide = SIDE_TOP;
+ side = ForgeDirection.UP;
+ } else if (tClickedBlock != Blocks.vine && tClickedBlock != Blocks.tallgrass
+ && tClickedBlock != Blocks.deadbush
+ && !tClickedBlock.isReplaceable(aWorld, aX, aY, aZ)) {
+ aX += side.offsetX;
+ aY += side.offsetY;
+ aZ += side.offsetZ;
+ }
+ final Block tReplacedBlock = aWorld.getBlock(aX, aY, aZ);
+
+ if (!tReplacedBlock.isReplaceable(aWorld, aX, aY, aZ)
+ || !mBlock.canReplace(aWorld, aX, aY, aZ, ordinalSide, aStack)) {
+ return false;
+ }
+
+ if (aStack.stackSize == 0 || (!aPlayer.canPlayerEdit(aX, aY, aZ, ordinalSide, aStack))) {
+ return false;
+ }
+
+ final MultiTileEntityContainer aMTEContainer = mBlock.mMultiTileEntityRegistry
+ .getNewTileEntityContainer(aWorld, aX, aY, aZ, aStack);
+
+ if (aMTEContainer == null) return false;
+
+ if (!aPlayer.isSneaking() && aMTEContainer.mTileEntity instanceof IMTE_OnlyPlaceableWhenSneaking mteSNeaking
+ && mteSNeaking.onlyPlaceableWhenSneaking()) {
+ return false;
+ }
+
+ if ((!(aMTEContainer.mTileEntity instanceof IMTE_IgnoreEntityCollisionWhenPlacing mteIgnoreCollision)
+ || !mteIgnoreCollision
+ .ignoreEntityCollisionWhenPlacing(aStack, aPlayer, aWorld, aX, aY, aZ, side, aHitX, aHitY, aHitZ))
+ && !aWorld.checkNoEntityCollision(AxisAlignedBB.getBoundingBox(aX, aY, aZ, aX + 1, aY + 1, aZ + 1))) {
+ return false;
+ }
+
+ if (aMTEContainer.mTileEntity instanceof IMTE_CanPlace mteCanPlace
+ && !mteCanPlace.canPlace(aStack, aPlayer, aWorld, aX, aY, aZ, side, aHitX, aHitY, aHitZ)) {
+ return false;
+ }
+
+ if (!aWorld.setBlock(aX, aY, aZ, aMTEContainer.mBlock, 15 - aMTEContainer.mBlockMetaData, 2)) {
+ return false;
+ }
+
+ aMTEContainer.setMultiTile(aWorld, aX, aY, aZ);
+
+ try {
+ if (((IMultiTileEntity) aMTEContainer.mTileEntity)
+ .onPlaced(aStack, aPlayer, aWorld, aX, aY, aZ, side, aHitX, aHitY, aHitZ)) {
+ aWorld.playSoundEffect(
+ aX + 0.5,
+ aY + 0.5,
+ aZ + 0.5,
+ aMTEContainer.mBlock.stepSound.func_150496_b(),
+ (aMTEContainer.mBlock.stepSound.getVolume() + 1) / 2,
+ aMTEContainer.mBlock.stepSound.getPitch() * 0.8F);
+ }
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("onPlaced", e);
+ }
+ try {
+ if (aMTEContainer.mTileEntity instanceof IMTE_HasMultiBlockMachineRelevantData mteData
+ && (mteData.hasMultiBlockMachineRelevantData())) {
+ GregTech_API.causeMachineUpdate(aWorld, aX, aY, aZ);
+ }
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("causeMachineUpdate", e);
+ }
+ try {
+ if (!aWorld.isRemote) {
+ aWorld.notifyBlockChange(aX, aY, aZ, tReplacedBlock);
+ aWorld.func_147453_f /* updateNeighborsAboutBlockChange */(aX, aY, aZ, aMTEContainer.mBlock);
+ }
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("notifyBlockChange", e);
+ }
+ try {
+ ((IMultiTileEntity) aMTEContainer.mTileEntity).onTileEntityPlaced();
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("onTileEntityPlaced", e);
+ }
+ try {
+ aWorld.func_147451_t /* updateAllLightTypes */(aX, aY, aZ);
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("updateAllLightTypes", e);
+ }
+
+ aStack.stackSize--;
+ return true;
+
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("onItemUse", e);
+ }
+ return false;
+ }
+
+ @Override
+ public void updateItemStack(ItemStack aStack) {
+ final MultiTileEntityClassContainer tContainer = mBlock.mMultiTileEntityRegistry.getClassContainer(aStack);
+ if (tContainer == null) return;
+ final MultiTileEntityContainer tTileEntityContainer = mBlock.mMultiTileEntityRegistry
+ .getCachedTileEntityContainer(aStack);
+ if (tTileEntityContainer != null && tTileEntityContainer.mTileEntity instanceof IItemUpdatable itemUpdatable) {
+ itemUpdatable.updateItemStack(aStack);
+ }
+ }
+
+ @Override
+ public void updateItemStack(ItemStack aStack, World aWorld, int aX, int aY, int aZ) {
+ final MultiTileEntityClassContainer tContainer = mBlock.mMultiTileEntityRegistry.getClassContainer(aStack);
+ if (tContainer == null) return;
+ final MultiTileEntityContainer tTileEntityContainer = mBlock.mMultiTileEntityRegistry
+ .getCachedTileEntityContainer(aStack);
+ if (tTileEntityContainer != null && tTileEntityContainer.mTileEntity instanceof IItemUpdatable itemUpdatable) {
+ itemUpdatable.updateItemStack(aStack, aWorld, aX, aY, aZ);
+ }
+ }
+
+ @Override
+ public int getItemStackLimit(ItemStack aStack) {
+ final MultiTileEntityClassContainer tContainer = mBlock.mMultiTileEntityRegistry.getClassContainer(aStack);
+ if (tContainer == null) return 1;
+ final MultiTileEntityContainer tTileEntityContainer = mBlock.mMultiTileEntityRegistry
+ .getCachedTileEntityContainer(aStack);
+ if (tTileEntityContainer != null
+ && tTileEntityContainer.mTileEntity instanceof IMTE_GetMaxStackSize maxStackSize) {
+ return maxStackSize.getMaxStackSize(aStack, tContainer.mStackSize);
+ }
+ return tContainer.mStackSize;
+ }
+
+ @Override
+ public void onCreated(ItemStack aStack, World aWorld, EntityPlayer aPlayer) {
+ updateItemStack(aStack);
+ }
+
+ @Override
+ public FluidStack getFluid(ItemStack aStack) {
+ final MultiTileEntityContainer tTileEntityContainer = mBlock.mMultiTileEntityRegistry
+ .getCachedTileEntityContainer(aStack);
+ if (tTileEntityContainer != null
+ && tTileEntityContainer.mTileEntity instanceof IFluidContainerItem fluidContainerItem) {
+ final FluidStack rFluid = fluidContainerItem.getFluid(aStack);
+ updateItemStack(aStack);
+ return rFluid;
+ }
+ return null;
+ }
+
+ @Override
+ public int getCapacity(ItemStack aStack) {
+ final MultiTileEntityContainer tTileEntityContainer = mBlock.mMultiTileEntityRegistry
+ .getCachedTileEntityContainer(aStack);
+ if (tTileEntityContainer != null
+ && tTileEntityContainer.mTileEntity instanceof IFluidContainerItem fluidContainerItem) {
+ final int rCapacity = fluidContainerItem.getCapacity(aStack);
+ updateItemStack(aStack);
+ return rCapacity;
+ }
+ return 0;
+ }
+
+ @Override
+ public int fill(ItemStack aStack, FluidStack aFluid, boolean aDoFill) {
+ final MultiTileEntityContainer tTileEntityContainer = mBlock.mMultiTileEntityRegistry
+ .getCachedTileEntityContainer(aStack);
+ if (tTileEntityContainer != null
+ && tTileEntityContainer.mTileEntity instanceof IFluidContainerItem fluidContainerItem) {
+ final int tFilled = fluidContainerItem.fill(aStack, aFluid, aDoFill);
+ updateItemStack(aStack);
+ return tFilled;
+ }
+ return 0;
+ }
+
+ @Override
+ public FluidStack drain(ItemStack aStack, int aMaxDrain, boolean aDoDrain) {
+ final MultiTileEntityContainer tTileEntityContainer = mBlock.mMultiTileEntityRegistry
+ .getCachedTileEntityContainer(aStack);
+ if (tTileEntityContainer != null
+ && tTileEntityContainer.mTileEntity instanceof IFluidContainerItem fluidContainerItem) {
+ final FluidStack rFluid = fluidContainerItem.drain(aStack, aMaxDrain, aDoDrain);
+ updateItemStack(aStack);
+ return rFluid;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean func_150936_a /* canPlaceAtSide */(World aWorld, int aX, int aY, int aZ, int ordinalSide,
+ EntityPlayer aPlayer, ItemStack aStack) {
+ return true;
+ }
+
+ @Override
+ public final String getUnlocalizedName() {
+ return mBlock.mMultiTileEntityRegistry.mNameInternal;
+ }
+
+ @Override
+ public final String getUnlocalizedName(ItemStack aStack) {
+ return mBlock.mMultiTileEntityRegistry.mNameInternal + "." + getDamage(aStack);
+ }
+
+ @Override
+ public int getSpriteNumber() {
+ return 0;
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public void registerIcons(IIconRegister aRegister) {
+ /* Empty */
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public IIcon getIconFromDamage(int aMeta) {
+ itemIcon = Items.bread.getIconFromDamage(0);
+ return itemIcon; /* Fixes Eating Animation Particles. */
+ }
+
+ @Override
+ public boolean doesContainerItemLeaveCraftingGrid(ItemStack aStack) {
+ return false;
+ }
+
+ @Override
+ public final boolean getShareTag() {
+ return true; // just to be sure
+ }
+
+ @Override
+ public int getItemEnchantability() {
+ return 0;
+ }
+
+ @Override
+ public boolean getIsRepairable(ItemStack aStack, ItemStack aMaterial) {
+ return false;
+ }
+
+ @Override
+ public ItemStack getContainerItem(ItemStack aStack) {
+ return null;
+ }
+
+ @Override
+ public final boolean hasContainerItem(ItemStack aStack) {
+ return getContainerItem(aStack) != null;
+ }
+
+ @Override
+ public boolean isBookEnchantable(ItemStack aStack, ItemStack aBook) {
+ return false;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/MultiTileEntityRegistry.java b/src/main/java/gregtech/api/multitileentity/MultiTileEntityRegistry.java
new file mode 100644
index 0000000000..3392d1ab41
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityRegistry.java
@@ -0,0 +1,290 @@
+package gregtech.api.multitileentity;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import net.minecraft.block.Block;
+import net.minecraft.init.Items;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+
+import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
+
+import appeng.core.CreativeTab;
+import cpw.mods.fml.common.Loader;
+import cpw.mods.fml.common.LoaderState;
+import cpw.mods.fml.common.registry.GameRegistry;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.multitileentity.base.MultiTileEntity;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
+import gregtech.api.util.GT_LanguageManager;
+import gregtech.api.util.GT_Util;
+import gregtech.api.util.GT_Utility;
+
+public class MultiTileEntityRegistry {
+
+ private static final HashMap<String, MultiTileEntityRegistry> NAMED_REGISTRIES = new HashMap<>();
+
+ // TODO: NBT sensitive or not? Starting with not for now
+ private static final ItemStackMap<MultiTileEntityRegistry> REGISTRIES = new ItemStackMap<>(false);
+ private static final HashSet<Class<?>> sRegisteredTileEntities = new HashSet<>();
+ private final HashMap<Integer, MultiTileEntityContainer> cachedTileEntityContainers = new HashMap<>();
+
+ public HashMap<Short, CreativeTab> mCreativeTabs = new HashMap<>();
+ public Map<Short, MultiTileEntityClassContainer> mRegistry = new HashMap<>();
+ public List<MultiTileEntityClassContainer> mRegistrations = new ArrayList<>();
+
+ public final String mNameInternal;
+ public final MultiTileEntityBlockInternal mBlock;
+
+ private static MultiTileEntityBlockInternal regblock(String aNameInternal, MultiTileEntityBlockInternal aBlock,
+ Class<? extends ItemBlock> aItemClass) {
+ GameRegistry.registerBlock(aBlock, aItemClass == null ? ItemBlock.class : aItemClass, aNameInternal);
+ return aBlock;
+ }
+
+ /**
+ * @param aNameInternal the internal Name of the Item
+ */
+ public MultiTileEntityRegistry(String aNameInternal) {
+ this(aNameInternal, new MultiTileEntityBlockInternal(), MultiTileEntityItemInternal.class);
+ }
+
+ /**
+ * @param aNameInternal the internal Name of the Item
+ */
+ public MultiTileEntityRegistry(String aNameInternal, MultiTileEntityBlockInternal aBlock,
+ Class<? extends ItemBlock> aItemClass) {
+ this(aNameInternal, regblock(aNameInternal, aBlock, aItemClass));
+ }
+
+ /**
+ * @param aNameInternal the internal Name of the Item
+ */
+ public MultiTileEntityRegistry(String aNameInternal, MultiTileEntityBlockInternal aBlock) {
+ if (!Loader.instance()
+ .isInState(LoaderState.PREINITIALIZATION)) {
+ throw new IllegalStateException(
+ "The MultiTileEntity Registry must be initialized during Preload Phase and not before");
+ }
+ mNameInternal = aNameInternal;
+ mBlock = aBlock;
+ GT_FML_LOGGER.info(aNameInternal + " " + Block.getIdFromBlock(aBlock) + "This is the answer");
+ mBlock.mMultiTileEntityRegistry = this;
+ REGISTRIES.put(new ItemStack(Item.getItemById(Block.getIdFromBlock(aBlock)), 1, GT_Values.W), this);
+ NAMED_REGISTRIES.put(mNameInternal, this);
+ }
+
+ public static TileEntity getCanonicalTileEntity(int aRegistryID, int aMultiTileEntityID) {
+ final MultiTileEntityRegistry tRegistry = getRegistry(aRegistryID);
+ if (tRegistry == null) return null;
+ final MultiTileEntityClassContainer tClassContainer = tRegistry.getClassContainer(aMultiTileEntityID);
+ if (tClassContainer == null) return null;
+ return tClassContainer.mCanonicalTileEntity;
+ }
+
+ public static MultiTileEntityRegistry getRegistry(int aRegistryID) {
+ return REGISTRIES.get(new ItemStack(Item.getItemById(aRegistryID), 1, GT_Values.W));
+ }
+
+ public static MultiTileEntityRegistry getRegistry(String aRegistryName) {
+ return NAMED_REGISTRIES.get(aRegistryName);
+ }
+
+ public MultiTileEntityClassContainer create(int aID, Class<? extends MultiTileEntity> aClass) {
+ return new MultiTileEntityClassContainer(this, aID, aClass);
+ }
+
+ /**
+ * Adds a new MultiTileEntity. It is highly recommended to do this in either the PreInit or the Init Phase. PostInit
+ * might not work well.
+ */
+ public ItemStack add(String aLocalised, String aCategoricalName, MultiTileEntityClassContainer aClassContainer) {
+ boolean tFailed = false;
+ if (GT_Utility.isStringInvalid(aLocalised)) {
+ GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: Localisation Missing!");
+ tFailed = true;
+ }
+ if (aClassContainer == null) {
+ GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: Class Container is null!");
+ tFailed = true;
+ } else {
+ if (aClassContainer.mClass == null) {
+ GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: Class inside Class Container is null!");
+ tFailed = true;
+ }
+ if (aClassContainer.mID == GT_Values.W) {
+ GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: Class Container uses Wildcard MetaData!");
+ tFailed = true;
+ }
+ if (aClassContainer.mID < 0) {
+ GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: Class Container uses negative MetaData!");
+ tFailed = true;
+ }
+ if (mRegistry.containsKey(aClassContainer.mID)) {
+ GT_FML_LOGGER.error(
+ "MULTI-TILE REGISTRY ERROR: Class Container uses occupied MetaData! (" + aClassContainer.mID + ")");
+ tFailed = true;
+ }
+ }
+ if (tFailed) {
+ GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: STACKTRACE START");
+ int i = 0;
+ for (StackTraceElement tElement : new Exception().getStackTrace()) if (i++ < 5 && !tElement.getClassName()
+ .startsWith("sun")) GT_FML_LOGGER.error("\tat " + tElement);
+ else break;
+ GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: STACKTRACE END");
+ return null;
+ }
+
+ GT_LanguageManager.addStringLocalization(mNameInternal + "." + aClassContainer.mID + ".name", aLocalised);
+ mRegistry.put(aClassContainer.mID, aClassContainer);
+ mLastRegisteredID = aClassContainer.mID;
+ mRegistrations.add(aClassContainer);
+
+ if (sRegisteredTileEntities.add(aClassContainer.mCanonicalTileEntity.getClass())) {
+ aClassContainer.mCanonicalTileEntity.onRegistrationFirst(this, aClassContainer.mID);
+ }
+ // // TODO: Recipe
+ // if (aRecipe != null && aRecipe.length > 1) {
+ // if (aRecipe[0] instanceof Object[]) aRecipe = (Object[])aRecipe[0];
+ // if (aRecipe.length > 2) CR.shaped(getItem(aClassContainer.mID), CR.DEF_REV_NCC, aRecipe);
+ // }
+ // // A simple special case to make it easier to add a Machine to Recipe Lists without having to worry
+ // about anything.
+ // String tRecipeMapName = aClassContainer.mParameters.getString(NBT_RECIPEMAP);
+ // if (GT_Utility.isStringValid(tRecipeMapName)) {RecipeMap tMap =
+ // RecipeMap.RECIPE_MAPS.get(tRecipeMapName); if (tMap != null)
+ // tMap.mRecipeMachineList.add(getItem(aClassContainer.mID));}
+ // tRecipeMapName = aClassContainer.mParameters.getString(NBT_FUELMAP);
+ // if (GT_Utility.isStringValid(tRecipeMapName)) {RecipeMap tMap =
+ // RecipeMap.RECIPE_MAPS.get(tRecipeMapName); if (tMap != null)
+ // tMap.mRecipeMachineList.add(getItem(aClassContainer.mID));}
+ //
+ return getItem(aClassContainer.mID);
+ }
+
+ public short mLastRegisteredID = GT_Values.W;
+
+ public ItemStack getItem() {
+ return getItem(mLastRegisteredID, 1, null);
+ }
+
+ public ItemStack getItem(int aID) {
+ return getItem(aID, 1, null);
+ }
+
+ public ItemStack getItem(int aID, NBTTagCompound aNBT) {
+ return getItem(aID, 1, aNBT);
+ }
+
+ public ItemStack getItem(int aID, long aAmount) {
+ return getItem(aID, aAmount, null);
+ }
+
+ public ItemStack getItem(int aID, long aAmount, NBTTagCompound aNBT) {
+ final ItemStack rStack = new ItemStack(mBlock, (int) aAmount, aID);
+ if (aNBT == null || aNBT.hasNoTags()) {
+ aNBT = new NBTTagCompound();
+ final MultiTileEntityContainer tTileEntityContainer = getNewTileEntityContainer(aID, aNBT);
+ if (tTileEntityContainer != null) ((IMultiTileEntity) tTileEntityContainer.mTileEntity).writeItemNBT(aNBT);
+ }
+ rStack.setTagCompound(aNBT);
+ return rStack;
+ }
+
+ public String getLocal(int aID) {
+ return StatCollector.translateToLocal(mNameInternal + "." + aID + ".name");
+ }
+
+ public MultiTileEntityClassContainer getClassContainer(int aID) {
+ return mRegistry.get((short) aID);
+ }
+
+ public MultiTileEntityClassContainer getClassContainer(ItemStack aStack) {
+ return mRegistry.get((short) Items.feather.getDamage(aStack));
+ }
+
+ public TileEntity getNewTileEntity(int aID) {
+ final MultiTileEntityContainer tContainer = getNewTileEntityContainer(null, 0, 0, 0, aID, null);
+ return tContainer == null ? null : tContainer.mTileEntity;
+ }
+
+ public MultiTileEntityContainer getNewTileEntityContainer(World aWorld, int aX, int aY, int aZ, int aID,
+ NBTTagCompound aNBT) {
+ final MultiTileEntityClassContainer tClass = mRegistry.get((short) aID);
+ if (tClass == null || tClass.mBlock == null) return null;
+ final MultiTileEntityContainer rContainer = new MultiTileEntityContainer(
+ (TileEntity) GT_Utility.callConstructor(tClass.mClass, -1, null, true),
+ tClass.mBlock,
+ tClass.mBlockMetaData);
+ if (rContainer.mTileEntity == null) return null;
+ rContainer.mTileEntity.setWorldObj(aWorld);
+ rContainer.mTileEntity.xCoord = aX;
+ rContainer.mTileEntity.yCoord = aY;
+ rContainer.mTileEntity.zCoord = aZ;
+ ((IMultiTileEntity) rContainer.mTileEntity).initFromNBT(
+ aNBT == null || aNBT.hasNoTags() ? tClass.mParameters : GT_Util.fuseNBT(aNBT, tClass.mParameters),
+ (short) aID,
+ (short) Block.getIdFromBlock(mBlock));
+ return rContainer;
+ }
+
+ public TileEntity getNewTileEntity(World aWorld, int aX, int aY, int aZ, int aID) {
+ final MultiTileEntityContainer tContainer = getNewTileEntityContainer(aWorld, aX, aY, aZ, aID, null);
+ return tContainer == null ? null : tContainer.mTileEntity;
+ }
+
+ public TileEntity getNewTileEntity(ItemStack aStack) {
+ final MultiTileEntityContainer tContainer = getNewTileEntityContainer(
+ null,
+ 0,
+ 0,
+ 0,
+ Items.feather.getDamage(aStack),
+ aStack.getTagCompound());
+ return tContainer == null ? null : tContainer.mTileEntity;
+ }
+
+ public TileEntity getNewTileEntity(World aWorld, int aX, int aY, int aZ, ItemStack aStack) {
+ final MultiTileEntityContainer tContainer = getNewTileEntityContainer(
+ aWorld,
+ aX,
+ aY,
+ aZ,
+ Items.feather.getDamage(aStack),
+ aStack.getTagCompound());
+ return tContainer == null ? null : tContainer.mTileEntity;
+ }
+
+ public MultiTileEntityContainer getCachedTileEntityContainer(ItemStack stack) {
+ MultiTileEntityContainer container = cachedTileEntityContainers.get(Items.feather.getDamage(stack));
+ if (container == null) {
+ container = getNewTileEntityContainer(stack);
+ cachedTileEntityContainers.put(Items.feather.getDamage(stack), container);
+ }
+ return container;
+ }
+
+ public MultiTileEntityContainer getNewTileEntityContainer(ItemStack aStack) {
+ return getNewTileEntityContainer(null, 0, 0, 0, Items.feather.getDamage(aStack), aStack.getTagCompound());
+ }
+
+ public MultiTileEntityContainer getNewTileEntityContainer(World aWorld, int aX, int aY, int aZ, ItemStack aStack) {
+ return getNewTileEntityContainer(aWorld, aX, aY, aZ, Items.feather.getDamage(aStack), aStack.getTagCompound());
+ }
+
+ public MultiTileEntityContainer getNewTileEntityContainer(int aID, NBTTagCompound aNBT) {
+ return getNewTileEntityContainer(null, 0, 0, 0, aID, aNBT);
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/base/MultiTileEntity.java b/src/main/java/gregtech/api/multitileentity/base/MultiTileEntity.java
new file mode 100644
index 0000000000..7c56d40296
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/base/MultiTileEntity.java
@@ -0,0 +1,1381 @@
+package gregtech.api.multitileentity.base;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+import static gregtech.api.enums.GT_Values.VALID_SIDES;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.block.Block;
+import net.minecraft.client.Minecraft;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.init.Items;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemBlock;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.network.Packet;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.MovingObjectPosition;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.world.Explosion;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.Fluid;
+
+import com.gtnewhorizons.modularui.common.internal.network.NetworkUtils;
+
+import cpw.mods.fml.common.registry.GameRegistry;
+import gregtech.api.GregTech_API;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.GT_Values.NBT;
+import gregtech.api.enums.Materials;
+import gregtech.api.enums.Mods;
+import gregtech.api.enums.SoundResource;
+import gregtech.api.enums.Textures;
+import gregtech.api.enums.Textures.BlockIcons.CustomIcon;
+import gregtech.api.gui.modularui.GT_UIInfos;
+import gregtech.api.interfaces.ITexture;
+import gregtech.api.metatileentity.CoverableTileEntity;
+import gregtech.api.metatileentity.GregTechTileClientEvents;
+import gregtech.api.multitileentity.MultiTileEntityBlockInternal;
+import gregtech.api.multitileentity.MultiTileEntityClassContainer;
+import gregtech.api.multitileentity.MultiTileEntityRegistry;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
+import gregtech.api.multitileentity.interfaces.SyncedMultiTileEntity;
+import gregtech.api.net.GT_Packet_MultiTileEntity;
+import gregtech.api.net.GT_Packet_New;
+import gregtech.api.net.data.CommonData;
+import gregtech.api.net.data.CoordinateData;
+import gregtech.api.net.data.MultiTileEntityData;
+import gregtech.api.objects.GT_ItemStack;
+import gregtech.api.objects.XSTR;
+import gregtech.api.render.TextureFactory;
+import gregtech.api.util.GT_Log;
+import gregtech.api.util.GT_ModHandler;
+import gregtech.api.util.GT_Util;
+import gregtech.api.util.GT_Utility;
+import gregtech.common.render.MultiTileBasicRender;
+import mcp.mobius.waila.api.IWailaConfigHandler;
+import mcp.mobius.waila.api.IWailaDataAccessor;
+
+public abstract class MultiTileEntity extends CoverableTileEntity
+ implements IMultiTileEntity.IMTE_BreakBlock, MultiTileBasicRender, SyncedMultiTileEntity {
+
+ private ITexture baseTexture = null;
+ private ITexture topOverlayTexture = null;
+ private ITexture bottomOverlayTexture = null;
+ private ITexture leftOverlayTexture = null;
+ private ITexture rightOverlayTexture = null;
+ private ITexture backOverlayTexture = null;
+ private ITexture frontOverlayTexture = null;
+
+ // A Bounding Box without having to constantly specify the Offset Coordinates.
+ protected static final float[] PX_BOX = { 0, 0, 0, 1, 1, 1 };
+
+ public Materials material = Materials._NULL;
+ protected final boolean isTicking; // If this TileEntity is ticking at all
+
+ // Checks if this TileEntity should refresh when the Block is being set.
+ // This way you can toggle this check at any time.
+ protected boolean shouldRefresh = true;
+
+ protected boolean needsBlockUpdate = false; // This Variable is for a buffered Block Update.
+ protected boolean forceFullSelectionBox = false; // This Variable is for forcing the Selection Box to be full.
+ protected boolean needsUpdate = false;
+ protected boolean hasInventoryChanged = false;
+ protected boolean isPainted = false;
+ @Nonnull
+ protected ForgeDirection facing = ForgeDirection.WEST; // Default to WEST, so it renders facing Left in the
+ // inventory
+ protected byte color;
+ protected int rgba = GT_Values.UNCOLORED;
+ private short mteID = GT_Values.W, mteRegistry = GT_Values.W;
+ private String customName = null;
+ private String ownerName = "";
+ private UUID ownerUUID = GT_Utility.defaultUuid;
+ private boolean lockUpgrade = false;
+
+ private final GT_Packet_MultiTileEntity fullPacket = new GT_Packet_MultiTileEntity(false);
+ private final GT_Packet_MultiTileEntity timedPacket = new GT_Packet_MultiTileEntity(false);
+ private final GT_Packet_MultiTileEntity graphicPacket = new GT_Packet_MultiTileEntity(false);
+
+ public MultiTileEntity(boolean isTicking) {
+ this.isTicking = isTicking;
+ }
+
+ @Override
+ public short getMultiTileEntityID() {
+ return mteID;
+ }
+
+ @Override
+ public short getMultiTileEntityRegistryID() {
+ return mteRegistry;
+ }
+
+ @Override
+ public void onRegistrationFirst(MultiTileEntityRegistry registry, short id) {
+ GameRegistry.registerTileEntity(getClass(), getTileEntityName());
+ }
+
+ @Override
+ public void initFromNBT(NBTTagCompound nbt, short mteID, short mteRegistry) {
+ if (this.mteID == mteID && this.mteRegistry == mteRegistry) {
+ return;
+ }
+ // Set ID and Registry ID.
+ this.mteID = mteID;
+ this.mteRegistry = mteRegistry;
+ // Read the Default Parameters from NBT.
+ if (nbt != null) readFromNBT(nbt);
+ }
+
+ @Override
+ public void loadTextures(String folder) {
+ // Loading the registry
+ for (SidedTextureNames textureName : SidedTextureNames.TEXTURES) {
+ ITexture texture;
+ try {
+ Minecraft.getMinecraft()
+ .getResourceManager()
+ .getResource(
+ new ResourceLocation(
+ Mods.GregTech.ID,
+ "textures/blocks/multitileentity/" + folder + "/" + textureName.getName() + ".png"));
+ texture = TextureFactory.of(new CustomIcon("multitileentity/" + folder + "/" + textureName.getName()));
+ } catch (IOException ignored) {
+ texture = TextureFactory.of(Textures.BlockIcons.VOID);
+ }
+ switch (textureName) {
+ case Top -> topOverlayTexture = texture;
+ case Bottom -> bottomOverlayTexture = texture;
+ case Back -> backOverlayTexture = texture;
+ case Front -> frontOverlayTexture = texture;
+ case Left -> leftOverlayTexture = texture;
+ case Right -> rightOverlayTexture = texture;
+ case Base -> baseTexture = texture;
+ }
+ }
+ }
+
+ @Override
+ public void copyTextures() {
+ // Loading an instance
+ final TileEntity tCanonicalTileEntity = MultiTileEntityRegistry
+ .getCanonicalTileEntity(getMultiTileEntityRegistryID(), getMultiTileEntityID());
+ if (!(tCanonicalTileEntity instanceof MultiTileEntity)) {
+ return;
+ }
+ final MultiTileEntity canonicalEntity = (MultiTileEntity) tCanonicalTileEntity;
+ baseTexture = canonicalEntity.baseTexture;
+ topOverlayTexture = canonicalEntity.topOverlayTexture;
+ bottomOverlayTexture = canonicalEntity.bottomOverlayTexture;
+ leftOverlayTexture = canonicalEntity.leftOverlayTexture;
+ rightOverlayTexture = canonicalEntity.rightOverlayTexture;
+ backOverlayTexture = canonicalEntity.backOverlayTexture;
+ frontOverlayTexture = canonicalEntity.frontOverlayTexture;
+ }
+
+ @Override
+ public ITexture getTexture(ForgeDirection side) {
+ if (facing == side) {
+ return TextureFactory.of(baseTexture, frontOverlayTexture);
+ }
+
+ if (facing.getOpposite() == side) {
+ return TextureFactory.of(baseTexture, backOverlayTexture);
+ }
+
+ if (side == ForgeDirection.UP) {
+ return TextureFactory.of(baseTexture, topOverlayTexture);
+ }
+
+ if (side == ForgeDirection.DOWN) {
+ return TextureFactory.of(baseTexture, bottomOverlayTexture);
+ }
+
+ if (facing.getRotation(ForgeDirection.DOWN) == side) {
+ return TextureFactory.of(baseTexture, rightOverlayTexture);
+ } else {
+ return TextureFactory.of(baseTexture, leftOverlayTexture);
+ }
+ }
+
+ @Override
+ public void readFromNBT(NBTTagCompound nbt) {
+ // Check if it is a World/Chunk-Loading Process calling readFromNBT
+ if (mteID == GT_Values.W || mteRegistry == GT_Values.W) {
+ // Read the ID Tags first
+ mteID = nbt.getShort(NBT.MTE_ID);
+ mteRegistry = nbt.getShort(NBT.MTE_REG);
+ // Add additional Default Parameters in case the Mod updated with new ones
+ final MultiTileEntityRegistry tRegistry = MultiTileEntityRegistry.getRegistry(mteRegistry);
+ if (tRegistry != null) {
+ final MultiTileEntityClassContainer tClass = tRegistry.getClassContainer(mteID);
+ if (tClass != null) {
+ // Add the Default Parameters. Useful for things that differ between different tiers/types of the
+ // same machine
+ nbt = GT_Util.fuseNBT(nbt, tClass.mParameters);
+ }
+ }
+ }
+ // read the Coords if it has them.
+ if (nbt.hasKey("x")) xCoord = nbt.getInteger("x");
+ if (nbt.hasKey("y")) yCoord = nbt.getInteger("y");
+ if (nbt.hasKey("z")) zCoord = nbt.getInteger("z");
+ // read the custom Name.
+ if (nbt.hasKey(NBT.DISPLAY)) customName = nbt.getCompoundTag(NBT.DISPLAY)
+ .getString(NBT.CUSTOM_NAME);
+
+ // And now everything else.
+ try {
+ if (nbt.hasKey(NBT.MATERIAL)) material = Materials.get(nbt.getString(NBT.MATERIAL));
+ if (nbt.hasKey(NBT.COLOR)) rgba = nbt.getInteger(NBT.COLOR);
+
+ ownerName = nbt.getString(NBT.OWNER);
+ try {
+ ownerUUID = UUID.fromString(nbt.getString(NBT.OWNER_UUID));
+ } catch (IllegalArgumentException e) {
+ ownerUUID = null;
+ }
+ if (nbt.hasKey(NBT.LOCK_UPGRADE)) lockUpgrade = nbt.getBoolean(NBT.LOCK_UPGRADE);
+ if (nbt.hasKey(NBT.FACING)) facing = ForgeDirection.getOrientation(nbt.getInteger(NBT.FACING));
+
+ readCoverNBT(nbt);
+ readTasksNBT(nbt);
+ readMultiTileNBT(nbt);
+
+ if (NetworkUtils.isDedicatedClient()) {
+ if (GregTech_API.sBlockIcons == null && nbt.hasKey(NBT.TEXTURE_FOLDER)) {
+ loadTextures(nbt.getString(NBT.TEXTURE_FOLDER));
+ } else {
+ copyTextures();
+ }
+ }
+
+ if (mSidedRedstone.length != 6) mSidedRedstone = new byte[] { 15, 15, 15, 15, 15, 15 };
+
+ updateCoverBehavior();
+
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("readFromNBT", e);
+ }
+ }
+
+ public void readMultiTileNBT(NBTTagCompound aNBT) {
+ /* Do Nothing */
+ }
+
+ protected void readTasksNBT(NBTTagCompound nbt) {}
+
+ @Override
+ public final void writeToNBT(NBTTagCompound aNBT) {
+ super.writeToNBT(aNBT);
+ // write the IDs
+ aNBT.setShort(NBT.MTE_ID, mteID);
+ aNBT.setShort(NBT.MTE_REG, mteRegistry);
+ // write the Custom Name
+ if (GT_Utility.isStringValid(customName)) {
+ final NBTTagCompound displayNBT;
+ if (aNBT.hasKey(NBT.DISPLAY)) {
+ displayNBT = aNBT.getCompoundTag(NBT.DISPLAY);
+ } else {
+ displayNBT = new NBTTagCompound();
+ aNBT.setTag(NBT.DISPLAY, displayNBT);
+ }
+ displayNBT.setString(NBT.CUSTOM_NAME, customName);
+ }
+
+ // write the rest
+ try {
+ aNBT.setString(NBT.OWNER, ownerName);
+ aNBT.setString(NBT.OWNER_UUID, ownerUUID == null ? "" : ownerUUID.toString());
+ aNBT.setBoolean(NBT.LOCK_UPGRADE, lockUpgrade);
+ aNBT.setInteger(NBT.FACING, facing.ordinal());
+
+ writeCoverNBT(aNBT, false);
+ writeTasksNBT(aNBT);
+ writeMultiTileNBT(aNBT);
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("writeToNBT", e);
+ }
+ }
+
+ public void writeMultiTileNBT(NBTTagCompound aNBT) {
+ /* Do Nothing */
+ }
+
+ protected void writeTasksNBT(NBTTagCompound aNBT) {}
+
+ @Override
+ public NBTTagCompound writeItemNBT(NBTTagCompound aNBT) {
+ writeCoverNBT(aNBT, true);
+ if (shouldSaveNBTToItemStack()) {
+ writeTasksNBT(aNBT);
+ writeMultiTileNBT(aNBT);
+ }
+ return aNBT;
+ }
+
+ protected boolean shouldSaveNBTToItemStack() {
+ return false;
+ }
+
+ @Override
+ public boolean useModularUI() {
+ return false;
+ }
+
+ @Override
+ public long getTimer() {
+ return 0;
+ }
+
+ @Override
+ public int getRandomNumber(int aRange) {
+ return XSTR.XSTR_INSTANCE.nextInt(aRange);
+ }
+
+ @Override
+ public TileEntity getTileEntity(int aX, int aY, int aZ) {
+ if (worldObj == null
+ || (ignoreUnloadedChunks && crossedChunkBorder(aX, aZ) && !worldObj.blockExists(aX, aY, aZ))) return null;
+ return GT_Util.getTileEntity(worldObj, aX, aY, aZ, true);
+ }
+
+ @Override
+ public boolean canUpdate() {
+ return isTicking && shouldRefresh;
+ }
+
+ @Override
+ public boolean shouldRefresh(Block aOldBlock, Block aNewBlock, int aOldMeta, int aNewMeta, World aWorld, int aX,
+ int aY, int aZ) {
+ return shouldRefresh || aOldBlock != aNewBlock;
+ }
+
+ @Override
+ public void updateEntity() {
+ super.updateEntity();
+ if (needsBlockUpdate) doBlockUpdate();
+ }
+
+ public void doBlockUpdate() {
+ final Block tBlock = getBlock(getCoords());
+ worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, tBlock);
+ if (this instanceof IMTE_IsProvidingStrongPower) {
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ if (getBlockAtSide(side)
+ .isNormalCube(worldObj, xCoord + side.offsetX, yCoord + side.offsetY, zCoord + side.offsetZ)) {
+ worldObj.notifyBlocksOfNeighborChange(
+ xCoord + side.offsetX,
+ yCoord + side.offsetY,
+ zCoord + side.offsetZ,
+ tBlock,
+ side.getOpposite()
+ .ordinal());
+ }
+ }
+ }
+ needsBlockUpdate = false;
+ }
+
+ @Override
+ public boolean shouldSideBeRendered(ForgeDirection side) {
+ final TileEntity tTileEntity = getTileEntityAtSideAndDistance(side, 1);
+ // TODO: check to an interface
+ // if (getBlockAtSide(aSide) == Blocks.glass) return false;
+ return tTileEntity instanceof IMultiTileEntity mte ? !mte.isSurfaceOpaque(side.getOpposite())
+ : !getBlockAtSide(side).isOpaqueCube();
+ }
+
+ @Override
+ public boolean isSurfaceOpaque(ForgeDirection side) {
+ return true;
+ }
+
+ @Override
+ public void setCustomName(String aName) {
+ customName = aName;
+ }
+
+ @Override
+ public String getCustomName() {
+ return GT_Utility.isStringValid(customName) ? customName : null;
+ }
+
+ @Override
+ public byte getColorization() {
+ // TODO
+ return 0;
+ }
+
+ @Override
+ public boolean unpaint() {
+ return false;
+ }
+
+ @Override
+ public byte setColorization(byte aColor) {
+ // TODO
+ return 0;
+ }
+
+ @Override
+ public boolean isPainted() {
+ return false;
+ }
+
+ @Override
+ public boolean paint(int aRGB) {
+ return false;
+ }
+
+ @Override
+ public boolean isFacingValid(ForgeDirection facing) {
+ return false;
+ }
+
+ @Override
+ public ForgeDirection getFrontFacing() {
+ return facing;
+ }
+
+ /**
+ * Sets the main facing to {aSide} and update as appropriately
+ *
+ * @return Whether the facing was changed
+ */
+ @Override
+ public boolean setMainFacing(ForgeDirection side) {
+ if (!isValidFacing(side)) return false;
+ facing = side;
+
+ issueClientUpdate();
+ issueBlockUpdate();
+ onFacingChange();
+ checkDropCover();
+ doEnetUpdate();
+
+ if (shouldTriggerBlockUpdate()) {
+ // If we're triggering a block update this will call onMachineBlockUpdate()
+ GregTech_API.causeMachineUpdate(worldObj, xCoord, yCoord, zCoord);
+ } else {
+ // If we're not trigger a cascading one, call the update here.
+ onMachineBlockUpdate();
+ }
+ return true;
+ }
+
+ @Override
+ public int getPaint() {
+ return this.rgba;
+ }
+
+ @Override
+ public ForgeDirection getBackFacing() {
+ return facing.getOpposite();
+ }
+
+ @Override
+ public boolean isValidFacing(ForgeDirection side) {
+ return side != ForgeDirection.UNKNOWN && getValidFacings()[side.ordinal()];
+ }
+
+ @Override
+ public boolean[] getValidFacings() {
+ return VALID_SIDES;
+ }
+
+ @Override
+ public void issueCoverUpdate(ForgeDirection side) {
+ super.issueCoverUpdate(side);
+ issueClientUpdate();
+ }
+
+ public AxisAlignedBB box(double[] aBox) {
+ return AxisAlignedBB.getBoundingBox(
+ xCoord + aBox[0],
+ yCoord + aBox[1],
+ zCoord + aBox[2],
+ xCoord + aBox[3],
+ yCoord + aBox[4],
+ zCoord + aBox[5]);
+ }
+
+ public boolean box(AxisAlignedBB aAABB, List<AxisAlignedBB> aList, double aMinX, double aMinY, double aMinZ,
+ double aMaxX, double aMaxY, double aMaxZ) {
+ final AxisAlignedBB tBox = box(aMinX, aMinY, aMinZ, aMaxX, aMaxY, aMaxZ);
+ return tBox.intersectsWith(aAABB) && aList.add(tBox);
+ }
+
+ @Override
+ public void onFacingChange() {
+ /* Do nothing */
+ }
+
+ public AxisAlignedBB box(double aMinX, double aMinY, double aMinZ, double aMaxX, double aMaxY, double aMaxZ) {
+ return AxisAlignedBB.getBoundingBox(
+ xCoord + aMinX,
+ yCoord + aMinY,
+ zCoord + aMinZ,
+ xCoord + aMaxX,
+ yCoord + aMaxY,
+ zCoord + aMaxZ);
+ }
+
+ @Override
+ public boolean shouldTriggerBlockUpdate() {
+ return false;
+ }
+
+ public boolean box(AxisAlignedBB aAABB, List<AxisAlignedBB> aList, double[] aBox) {
+ final AxisAlignedBB tBox = box(aBox[0], aBox[1], aBox[2], aBox[3], aBox[4], aBox[5]);
+ return tBox.intersectsWith(aAABB) && aList.add(tBox);
+ }
+
+ @Override
+ public void onMachineBlockUpdate() {
+ /* Do nothing */
+ }
+
+ public boolean box(AxisAlignedBB aAABB, List<AxisAlignedBB> aList, float[] aBox) {
+ final AxisAlignedBB tBox = box(aBox[0], aBox[1], aBox[2], aBox[3], aBox[4], aBox[5]);
+ return tBox.intersectsWith(aAABB) && aList.add(tBox);
+ }
+
+ public boolean box(AxisAlignedBB aAABB, List<AxisAlignedBB> aList) {
+ final AxisAlignedBB tBox = box(PX_BOX);
+ return tBox.intersectsWith(aAABB) && aList.add(tBox);
+ }
+
+ public AxisAlignedBB box(float[] aBox) {
+ return AxisAlignedBB.getBoundingBox(
+ xCoord + aBox[0],
+ yCoord + aBox[1],
+ zCoord + aBox[2],
+ xCoord + aBox[3],
+ yCoord + aBox[4],
+ zCoord + aBox[5]);
+ }
+
+ public boolean box(Block aBlock) {
+ aBlock.setBlockBounds(0, 0, 0, 1, 1, 1);
+ return true;
+ }
+
+ /**
+ * Causes a general Texture update.
+ * <p/>
+ * Only used Client Side to mark Blocks dirty.
+ */
+ @Override
+ public void issueTextureUpdate() {
+ if (!isTicking) {
+ markBlockForUpdate();
+ } else {
+ needsUpdate = true;
+ }
+ }
+
+ public boolean box(Block aBlock, double[] aBox) {
+ aBlock.setBlockBounds(
+ (float) aBox[0],
+ (float) aBox[1],
+ (float) aBox[2],
+ (float) aBox[3],
+ (float) aBox[4],
+ (float) aBox[5]);
+ return true;
+ }
+
+ protected void markBlockForUpdate() {
+ worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
+ // worldObj.func_147479_m(xCoord, yCoord, zCoord);
+ needsUpdate = false;
+ }
+
+ public boolean box(Block aBlock, float[] aBox) {
+ aBlock.setBlockBounds(aBox[0], aBox[1], aBox[2], aBox[3], aBox[4], aBox[5]);
+ return true;
+ }
+
+ @Override
+ public void onTileEntityPlaced() {
+ /* empty */
+ }
+
+ public boolean box(Block aBlock, double aMinX, double aMinY, double aMinZ, double aMaxX, double aMaxY,
+ double aMaxZ) {
+ aBlock.setBlockBounds((float) aMinX, (float) aMinY, (float) aMinZ, (float) aMaxX, (float) aMaxY, (float) aMaxZ);
+ return true;
+ }
+
+ @Override
+ public void setShouldRefresh(boolean aShouldRefresh) {
+ shouldRefresh = aShouldRefresh;
+ }
+
+ /**
+ * shouldJoinIc2Enet - defaults to false, override to change
+ */
+ @Override
+ public boolean shouldJoinIc2Enet() {
+ return false;
+ }
+
+ @Override
+ public final void addCollisionBoxesToList(AxisAlignedBB aAABB, List<AxisAlignedBB> aList, Entity aEntity) {
+ box(getCollisionBoundingBoxFromPool(), aAABB, aList);
+ }
+
+ /**
+ * Simple Function to prevent Block Updates from happening multiple times within the same Tick.
+ */
+ @Override
+ public final void issueBlockUpdate() {
+ if (isTicking) needsBlockUpdate = true;
+ else doBlockUpdate();
+ }
+
+ @Override
+ public boolean isStillValid() {
+ return !isInvalid();
+ }
+
+ @Override
+ public boolean allowCoverOnSide(ForgeDirection side, GT_ItemStack aCoverID) {
+ return true;
+ }
+
+ public AxisAlignedBB box() {
+ return AxisAlignedBB.getBoundingBox(xCoord, yCoord, zCoord, xCoord + 1, yCoord + 1, zCoord + 1);
+ }
+
+ public boolean box(AxisAlignedBB aBox, AxisAlignedBB aAABB, List<AxisAlignedBB> aList) {
+ return aBox != null && aBox.intersectsWith(aAABB) && aList.add(aBox);
+ }
+
+ public float[] shrunkBox() {
+ return PX_BOX;
+ }
+
+ @Override
+ public void setBlockBoundsBasedOnState(Block aBlock) {
+ box(aBlock);
+ }
+
+ @Override
+ public AxisAlignedBB getCollisionBoundingBoxFromPool() {
+ return box();
+ }
+
+ @Override
+ public AxisAlignedBB getSelectedBoundingBoxFromPool() {
+ if (forceFullSelectionBox) return box();
+ return box(shrunkBox());
+ }
+
+ @Override
+ public ItemStack getPickBlock(MovingObjectPosition aTarget) {
+ final MultiTileEntityRegistry tRegistry = MultiTileEntityRegistry.getRegistry(mteRegistry);
+ return tRegistry == null ? null : tRegistry.getItem(mteID, writeItemNBT(new NBTTagCompound()));
+ }
+
+ @Override
+ public void onBlockAdded() {}
+
+ @Override
+ public String getOwnerName() {
+ if (GT_Utility.isStringInvalid(ownerName)) return "Player";
+ return ownerName;
+ }
+
+ @Override
+ public String setOwnerName(String aName) {
+ if (GT_Utility.isStringInvalid(aName)) return ownerName = "Player";
+ return ownerName = aName;
+ }
+
+ @Override
+ public UUID getOwnerUuid() {
+ return ownerUUID;
+ }
+
+ @Override
+ public void setOwnerUuid(UUID uuid) {
+ ownerUUID = uuid;
+ }
+
+ @Override
+ public boolean onPlaced(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ,
+ ForgeDirection side, float aHitX, float aHitY, float aHitZ) {
+ facing = getSideForPlayerPlacing(aPlayer, facing, getValidFacings());
+ setOwnerUuid(aPlayer.getUniqueID());
+ setOwnerName(aPlayer.getDisplayName());
+ onFacingChange();
+ return true;
+ }
+
+ @Override
+ public boolean allowInteraction(Entity aEntity) {
+ return true;
+ }
+
+ public boolean allowRightclick(Entity aEntity) {
+ return allowInteraction(aEntity);
+ }
+
+ @Override
+ public boolean onBlockActivated(EntityPlayer aPlayer, ForgeDirection side, float aX, float aY, float aZ) {
+ try {
+ return allowRightclick(aPlayer) && onRightClick(aPlayer, side, aX, aY, aZ);
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("onBlockActivated Failed", e);
+ e.printStackTrace(GT_Log.err);
+ return true;
+ }
+ }
+
+ @Override
+ public boolean onRightClick(EntityPlayer aPlayer, ForgeDirection side, float aX, float aY, float aZ) {
+ if (isClientSide()) {
+ // Configure Cover, sneak can also be: screwdriver, wrench, side cutter, soldering iron
+ if (aPlayer.isSneaking()) {
+ final ForgeDirection tSide = (getCoverIDAtSide(side) == 0)
+ ? GT_Utility.determineWrenchingSide(side, aX, aY, aZ)
+ : side;
+ return (getCoverBehaviorAtSideNew(tSide).hasCoverGUI());
+ } else if (getCoverBehaviorAtSideNew(side).onCoverRightclickClient(side, this, aPlayer, aX, aY, aZ)) {
+ return true;
+ }
+
+ if (!getCoverInfoAtSide(side).isGUIClickable()) return false;
+ } else { // server side
+ if (!privateAccess() || aPlayer.getDisplayName()
+ .equalsIgnoreCase(getOwnerName())) {
+ final ItemStack tCurrentItem = aPlayer.inventory.getCurrentItem();
+ final ForgeDirection wrenchSide = GT_Utility.determineWrenchingSide(side, aX, aY, aZ);
+
+ if (tCurrentItem != null) {
+ if (getColorization() >= 0
+ && GT_Utility.areStacksEqual(new ItemStack(Items.water_bucket, 1), tCurrentItem)) {
+ // TODO (Colorization)
+ }
+
+ if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sWrenchList))
+ return onWrenchRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ, tCurrentItem);
+ if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sScrewdriverList))
+ return onScrewdriverRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ, tCurrentItem);
+ if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sHardHammerList))
+ return onHammerRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ, tCurrentItem);
+ if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sSoftHammerList))
+ return onMalletRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ, tCurrentItem);
+ if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sSolderingToolList))
+ return onSolderingRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ, tCurrentItem);
+ if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sWireCutterList))
+ return onWireCutterRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ, tCurrentItem);
+
+ final ForgeDirection coverSide = getCoverIDAtSide(side) == 0 ? wrenchSide : side;
+
+ if (getCoverIDAtSide(coverSide) == 0) {
+ if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sCovers.keySet())) {
+ if (GregTech_API.getCoverBehaviorNew(tCurrentItem)
+ .isCoverPlaceable(coverSide, tCurrentItem, this)
+ && allowCoverOnSide(coverSide, new GT_ItemStack(tCurrentItem))) {
+ setCoverItemAtSide(coverSide, tCurrentItem);
+ if (!aPlayer.capabilities.isCreativeMode) tCurrentItem.stackSize--;
+ GT_Utility.sendSoundToPlayers(
+ worldObj,
+ SoundResource.IC2_TOOLS_WRENCH,
+ 1.0F,
+ -1,
+ xCoord,
+ yCoord,
+ zCoord);
+ issueClientUpdate();
+ }
+ sendCoverDataIfNeeded();
+ return true;
+ }
+ } else {
+ if (GT_Utility.isStackInList(tCurrentItem, GregTech_API.sCrowbarList)) {
+ if (GT_ModHandler.damageOrDechargeItem(tCurrentItem, 1, 1000, aPlayer)) {
+ GT_Utility.sendSoundToPlayers(
+ worldObj,
+ SoundResource.RANDOM_BREAK,
+ 1.0F,
+ -1,
+ xCoord,
+ yCoord,
+ zCoord);
+ dropCover(coverSide, side, false);
+ }
+ sendCoverDataIfNeeded();
+ return true;
+ }
+ }
+ } else if (aPlayer.isSneaking()) { // Sneak click, no tool -> open cover config if possible.
+ side = (getCoverIDAtSide(side) == 0) ? GT_Utility.determineWrenchingSide(side, aX, aY, aZ) : side;
+ return getCoverIDAtSide(side) > 0 && getCoverBehaviorAtSideNew(side).onCoverShiftRightClick(
+ side,
+ getCoverIDAtSide(side),
+ getComplexCoverDataAtSide(side),
+ this,
+ aPlayer);
+ }
+
+ if (getCoverBehaviorAtSideNew(side).onCoverRightClick(
+ side,
+ getCoverIDAtSide(side),
+ getComplexCoverDataAtSide(side),
+ this,
+ aPlayer,
+ aX,
+ aY,
+ aZ)) return true;
+
+ if (!getCoverInfoAtSide(side).isGUIClickable()) return false;
+
+ if (aPlayer.getHeldItem() != null && aPlayer.getHeldItem()
+ .getItem() instanceof ItemBlock) {
+ return false;
+ }
+
+ return openModularUi(aPlayer, side);
+ }
+ }
+ return false;
+ }
+
+ public boolean hasGui(ForgeDirection side) {
+ return false;
+ }
+
+ boolean openModularUi(EntityPlayer aPlayer, ForgeDirection side) {
+ if (!hasGui(side) || !isServerSide()) {
+ System.out.println("No GUI or Not Serverside");
+ return false;
+ }
+
+ GT_UIInfos.openGTTileEntityUI(this, aPlayer);
+ System.out.println("Trying to open a UI");
+ return true;
+ }
+
+ public boolean onWrenchRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
+ float aY, float aZ, ItemStack aTool) {
+ if (setMainFacing(wrenchSide)) {
+ GT_ModHandler.damageOrDechargeItem(tCurrentItem, 1, 1000, aPlayer);
+ GT_Utility.sendSoundToPlayers(worldObj, SoundResource.IC2_TOOLS_WRENCH, 1.0F, -1, xCoord, yCoord, zCoord);
+ }
+ return onWrenchRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ);
+ }
+
+ public boolean onScrewdriverRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide,
+ float aX, float aY, float aZ, ItemStack aTool) {
+ if (GT_ModHandler.damageOrDechargeItem(tCurrentItem, 1, 200, aPlayer)) {
+ setCoverDataAtSide(
+ wrenchSide,
+ getCoverBehaviorAtSideNew(wrenchSide).onCoverScrewdriverClick(
+ wrenchSide,
+ getCoverIDAtSide(wrenchSide),
+ getComplexCoverDataAtSide(wrenchSide),
+ this,
+ aPlayer,
+ aX,
+ aY,
+ aZ));
+ // TODO: Update connections!
+ GT_Utility.sendSoundToPlayers(worldObj, SoundResource.IC2_TOOLS_WRENCH, 1.0F, -1, xCoord, yCoord, zCoord);
+ }
+ return onScrewdriverRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ);
+ }
+
+ public boolean onHammerRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
+ float aY, float aZ, ItemStack aTool) {
+
+ return onHammerRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ);
+ }
+
+ public boolean onMalletRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
+ float aY, float aZ, ItemStack aTool) {
+
+ return onMalletRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ);
+ }
+
+ public boolean onSolderingRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide,
+ float aX, float aY, float aZ, ItemStack aTool) {
+
+ return onSolderingRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ);
+ }
+
+ public boolean onWireCutterRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide,
+ float aX, float aY, float aZ, ItemStack aTool) {
+
+ return onWireCutterRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ);
+ }
+
+ @Deprecated
+ public boolean onHammerRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
+ float aY, float aZ) {
+ return true;
+ }
+
+ @Deprecated
+ public boolean onSolderingRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide,
+ float aX, float aY, float aZ) {
+ return true;
+ }
+
+ @Deprecated
+ public boolean onMalletRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
+ float aY, float aZ) {
+ return true;
+ }
+
+ @Deprecated
+ public boolean onWireCutterRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide,
+ float aX, float aY, float aZ) {
+ return true;
+ }
+
+ @Deprecated
+ public boolean onWrenchRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
+ float aY, float aZ) {
+ return true;
+ }
+
+ @Deprecated
+ public boolean onScrewdriverRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide,
+ float aX, float aY, float aZ) {
+ return true;
+ }
+
+ @Override
+ public float getExplosionResistance(Entity aExploder, double aExplosionX, double aExplosionY, double aExplosionZ) {
+ return getExplosionResistance();
+ }
+
+ @Override
+ public float getExplosionResistance() {
+ return 10.0F;
+ }
+
+ @Override
+ public void onExploded(Explosion aExplosion) {}
+
+ @Override
+ public boolean isSideSolid(ForgeDirection side) {
+ return true;
+ }
+
+ @Override
+ public ArrayList<ItemStack> getDrops(int aFortune, boolean aSilkTouch) {
+ final ArrayList<ItemStack> rList = new ArrayList<>();
+ final MultiTileEntityRegistry tRegistry = MultiTileEntityRegistry.getRegistry(getMultiTileEntityRegistryID());
+ if (tRegistry != null) rList.add(tRegistry.getItem(getMultiTileEntityID(), writeItemNBT(new NBTTagCompound())));
+ return rList;
+ }
+
+ @Override
+ public boolean breakBlock() {
+ isDead = true;
+ onBaseTEDestroyed();
+ return false;
+ }
+
+ @Override
+ public boolean getSubItems(MultiTileEntityBlockInternal aBlock, Item aItem, CreativeTabs aTab,
+ List<ItemStack> aList, short aID) {
+ return true;
+ }
+
+ @Override
+ public boolean recolourBlock(ForgeDirection side, byte aColor) {
+ // if (aColor > 15 || aColor < -1) aColor = -1;
+ // if(paint((byte) (aColor + 1))) {
+ //// updateClientData();
+ //// causeBlockUpdate();
+ // return true;
+ // }
+ // if (unpaint()) {updateClientData(); causeBlockUpdate(); return T;}
+ // mColor = (byte) (aColor + 1);
+ //// if (canAccessData()) mMetaTileEntity.onColorChangeServer(aColor);
+ return false;
+ }
+
+ @Override
+ public boolean playerOwnsThis(EntityPlayer aPlayer, boolean aCheckPrecicely) {
+ if (aCheckPrecicely || privateAccess() || (ownerName.length() == 0))
+ if ((ownerName.length() == 0) && isServerSide()) {
+ setOwnerName(aPlayer.getDisplayName());
+ setOwnerUuid(aPlayer.getUniqueID());
+ } else return !privateAccess() || aPlayer.getDisplayName()
+ .equals("Player") || ownerName.equals("Player") || ownerName.equals(aPlayer.getDisplayName());
+ return true;
+ }
+
+ @Override
+ public boolean privateAccess() {
+ return lockUpgrade;
+ }
+
+ /**
+ * @return a Packet containing all Data which has to be synchronised to the Client - Override as needed
+ */
+ public GT_Packet_MultiTileEntity getClientDataPacket() {
+
+ final GT_Packet_MultiTileEntity packet = new GT_Packet_MultiTileEntity(false);
+ return packet;
+ }
+
+ @Override
+ public void sendClientData(EntityPlayerMP aPlayer) {
+ if (worldObj == null || worldObj.isRemote) return;
+ final GT_Packet_New tPacket = getClientDataPacket();
+ if (aPlayer == null) {
+ GT_Values.NW.sendPacketToAllPlayersInRange(worldObj, tPacket, getXCoord(), getZCoord());
+ } else {
+ GT_Values.NW.sendToPlayer(tPacket, aPlayer);
+ }
+ sendCoverDataIfNeeded();
+ }
+
+ @Override
+ public boolean receiveClientData(int aEventID, int aValue) {
+ super.receiveClientEvent(aEventID, aValue);
+ if (isClientSide()) {
+ issueTextureUpdate();
+ switch (aEventID) {
+ case GregTechTileClientEvents.CHANGE_COMMON_DATA:
+ facing = ForgeDirection.getOrientation(aValue & 7);
+ // mActive = ((aValue & 8) != 0);
+ mRedstone = ((aValue & 16) != 0);
+ // mLockUpgrade = ((aValue&32) != 0);
+ // mWorks = ((aValue & 64) != 0);
+ break;
+ case GregTechTileClientEvents.CHANGE_CUSTOM_DATA:
+ // Nothing here, currently
+ break;
+ case GregTechTileClientEvents.CHANGE_COLOR:
+ if (aValue > 16 || aValue < 0) aValue = 0;
+ color = (byte) aValue;
+ break;
+ case GregTechTileClientEvents.CHANGE_REDSTONE_OUTPUT:
+ mSidedRedstone[0] = (byte) ((aValue & 1) == 1 ? 15 : 0);
+ mSidedRedstone[1] = (byte) ((aValue & 2) == 2 ? 15 : 0);
+ mSidedRedstone[2] = (byte) ((aValue & 4) == 4 ? 15 : 0);
+ mSidedRedstone[3] = (byte) ((aValue & 8) == 8 ? 15 : 0);
+ mSidedRedstone[4] = (byte) ((aValue & 16) == 16 ? 15 : 0);
+ mSidedRedstone[5] = (byte) ((aValue & 32) == 32 ? 15 : 0);
+ break;
+ // case GregTechTileClientEvents.DO_SOUND:
+ // if (mTickTimer > 20)
+ // doSound((byte) aValue, xCoord + 0.5, yCoord + 0.5, zCoord + 0.5);
+ // break;
+ // case GregTechTileClientEvents.START_SOUND_LOOP:
+ // if (mTickTimer > 20)
+ // startSoundLoop((byte) aValue, xCoord + 0.5, yCoord + 0.5, zCoord + 0.5);
+ // break;
+ // case GregTechTileClientEvents.STOP_SOUND_LOOP:
+ // if (mTickTimer > 20)
+ // stopSoundLoop((byte) aValue, xCoord + 0.5, yCoord + 0.5, zCoord + 0.5);
+ // break;
+ // case GregTechTileClientEvents.CHANGE_LIGHT:
+ // mLightValue = (byte) aValue;
+ // break;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Packet getDescriptionPacket() {
+ issueClientUpdate();
+ return null;
+ }
+
+ @Override
+ public void getWailaBody(ItemStack itemStack, List<String> currentTip, IWailaDataAccessor accessor,
+ IWailaConfigHandler config) {
+ super.getWailaBody(itemStack, currentTip, accessor, config);
+ currentTip.add(String.format("Facing: %s", getFrontFacing().name()));
+ }
+
+ @Override
+ public void getWailaNBTData(EntityPlayerMP player, TileEntity tile, NBTTagCompound tag, World world, int x, int y,
+ int z) {
+ super.getWailaNBTData(player, tile, tag, world, x, y, z);
+ }
+
+ @Override
+ public ArrayList<String> getDebugInfo(EntityPlayer aPlayer, int aLogLevel) {
+ final ArrayList<String> tList = new ArrayList<>();
+ if (aLogLevel > 2) {
+ tList.add(
+ "MultiTileRegistry-ID: " + EnumChatFormatting.BLUE
+ + mteRegistry
+ + EnumChatFormatting.RESET
+ + " MultiTile-ID: "
+ + EnumChatFormatting.BLUE
+ + mteID
+ + EnumChatFormatting.RESET);
+ }
+
+ addDebugInfo(aPlayer, aLogLevel, tList);
+
+ return tList;
+ }
+
+ protected void addDebugInfo(EntityPlayer aPlayer, int aLogLevel, ArrayList<String> tList) {
+ /* Do nothing */
+ }
+
+ /**
+ * Energy - Do nothing by Default
+ */
+ @Override
+ public boolean isUniversalEnergyStored(long aEnergyAmount) {
+ return false;
+ }
+
+ @Override
+ public long getUniversalEnergyStored() {
+ return 0;
+ }
+
+ @Override
+ public long getUniversalEnergyCapacity() {
+ return 0;
+ }
+
+ @Override
+ public long getOutputAmperage() {
+ return 0;
+ }
+
+ @Override
+ public long getOutputVoltage() {
+ return 0;
+ }
+
+ @Override
+ public long getInputAmperage() {
+ return 0;
+ }
+
+ @Override
+ public long getInputVoltage() {
+ return 0;
+ }
+
+ @Override
+ public boolean decreaseStoredEnergyUnits(long energy, boolean ignoreTooLittleEnergy) {
+ return false;
+ }
+
+ @Override
+ public boolean increaseStoredEnergyUnits(long energy, boolean ignoreTooMuchEnergy) {
+ return false;
+ }
+
+ @Override
+ public boolean drainEnergyUnits(ForgeDirection side, long aVoltage, long aAmperage) {
+ return false;
+ }
+
+ @Override
+ public long getAverageElectricInput() {
+ return 0;
+ }
+
+ @Override
+ public long getAverageElectricOutput() {
+ return 0;
+ }
+
+ @Override
+ public long getStoredEU() {
+ return 0;
+ }
+
+ @Override
+ public long getEUCapacity() {
+ return 0;
+ }
+
+ @Override
+ public long injectEnergyUnits(ForgeDirection side, long aVoltage, long aAmperage) {
+ return 0;
+ }
+
+ @Override
+ public boolean inputEnergyFrom(ForgeDirection side) {
+ return false;
+ }
+
+ @Override
+ public boolean outputsEnergyTo(ForgeDirection side) {
+ return false;
+ }
+
+ /**
+ * Inventory - Do nothing by default
+ */
+
+ @Override
+ public boolean hasInventoryBeenModified() {
+ return false;
+ }
+
+ @Override
+ public boolean isValidSlot(int aIndex) {
+ return false;
+ }
+
+ @Override
+ public boolean addStackToSlot(int aIndex, ItemStack aStack) {
+ return false;
+ }
+
+ @Override
+ public boolean addStackToSlot(int aIndex, ItemStack aStack, int aAmount) {
+ return false;
+ }
+
+ @Override
+ public void markInventoryBeenModified() {
+ hasInventoryChanged = true;
+ }
+
+ /*
+ * Cover Helpers
+ */
+
+ public boolean coverLetsFluidIn(ForgeDirection side, Fluid aFluid) {
+ return getCoverInfoAtSide(side).letsFluidIn(aFluid);
+ }
+
+ public boolean coverLetsFluidOut(ForgeDirection side, Fluid aFluid) {
+ return getCoverInfoAtSide(side).letsFluidOut(aFluid);
+ }
+
+ public boolean coverLetsEnergyIn(ForgeDirection side) {
+ return getCoverInfoAtSide(side).letsEnergyIn();
+ }
+
+ public boolean coverLetsEnergyOut(ForgeDirection side) {
+ return getCoverInfoAtSide(side).letsEnergyOut();
+ }
+
+ public boolean coverLetsItemsIn(ForgeDirection side, int aSlot) {
+ return getCoverInfoAtSide(side).letsItemsIn(aSlot);
+ }
+
+ public boolean coverLetsItemsOut(ForgeDirection side, int aSlot) {
+ return getCoverInfoAtSide(side).letsItemsOut(aSlot);
+ }
+
+ @Override
+ public ItemStack getStackForm(long aAmount) {
+ return new ItemStack(Item.getItemById(getMultiTileEntityRegistryID()), (int) aAmount, getMultiTileEntityID());
+ }
+
+ protected enum SidedTextureNames {
+
+ Base("base"),
+ Left("left"),
+ Right("right"),
+ Top("top"),
+ Bottom("bottom"),
+ Back("back"),
+ Front("front");
+
+ private final String name;
+ public static final SidedTextureNames[] TEXTURES = { Base, Left, Right, Top, Bottom, Back, Front };
+
+ SidedTextureNames(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ protected enum StatusTextures {
+
+ Active("active", false),
+ ActiveWithGlow("active_glow", true),
+ Inactive("inactive", false),
+ InactiveWithGlow("inactive_glow", true);
+
+ private final String name;
+ private final boolean hasGlow;
+ public static final StatusTextures[] TEXTURES = { Active, ActiveWithGlow, Inactive, InactiveWithGlow };
+
+ StatusTextures(String name, boolean hasGlow) {
+ this.name = name;
+ this.hasGlow = hasGlow;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean hasGlow() {
+ return hasGlow;
+ }
+ }
+
+ @Override
+ public void getFullPacketData(GT_Packet_MultiTileEntity packet) {
+ packet.addData(new CoordinateData(getCoords()));
+ packet.addData(new CommonData(mStrongRedstone, color, (byte) 0));
+ packet.addData(new MultiTileEntityData(mteRegistry, mteID));
+ }
+
+ @Override
+ public void getGraphicPacketData(GT_Packet_MultiTileEntity packet) {
+ packet.addData(new CoordinateData(getCoords()));
+ packet.addData(new MultiTileEntityData(mteRegistry, mteID));
+ }
+
+ @Override
+ public void getTimedPacketData(GT_Packet_MultiTileEntity packet) {
+ packet.addData(new CoordinateData(getCoords()));
+ packet.addData(new MultiTileEntityData(mteRegistry, mteID));
+ }
+
+ @Override
+ public void sendFullPacket(@Nonnull EntityPlayerMP player) {
+ fullPacket.clearData();
+ getFullPacketData(fullPacket);
+ GT_Values.NW.sendToPlayer(fullPacket, player);
+ }
+
+ @Override
+ public void sendGraphicPacket() {
+ graphicPacket.clearData();
+ getGraphicPacketData(graphicPacket);
+ GT_Values.NW.sendPacketToAllPlayersInRange(worldObj, graphicPacket, getXCoord(), getZCoord());
+ }
+
+ @Override
+ public void sendTimedPacket() {
+ timedPacket.clearData();
+ getTimedPacketData(timedPacket);
+ GT_Values.NW.sendPacketToAllPlayersInRange(worldObj, timedPacket, getXCoord(), getZCoord());
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/base/NonTickableMultiTileEntity.java b/src/main/java/gregtech/api/multitileentity/base/NonTickableMultiTileEntity.java
new file mode 100644
index 0000000000..2837a88180
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/base/NonTickableMultiTileEntity.java
@@ -0,0 +1,62 @@
+package gregtech.api.multitileentity.base;
+
+import static gregtech.api.enums.GT_Values.NW;
+
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.network.Packet;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import gregtech.api.net.GT_Packet_SendCoverData;
+import gregtech.api.util.ISerializableObject;
+import gregtech.common.covers.CoverInfo;
+
+public abstract class NonTickableMultiTileEntity extends MultiTileEntity {
+
+ boolean mConstructed = false; // Keeps track of whether this TE has been constructed and placed in the world
+
+ public NonTickableMultiTileEntity() {
+ super(false);
+ }
+
+ @Override
+ public void issueClientUpdate() {
+ if (worldObj != null && !worldObj.isRemote) {
+ sendClientData(null);
+ sendGraphicPacket();
+ }
+ }
+
+ @Override
+ public Packet getDescriptionPacket() {
+ // We should have a world object and have been constructed by this point
+ mConstructed = true;
+
+ super.getDescriptionPacket();
+ // We don't get ticked, so if we have any cover data that needs to be sent, send it now
+ sendCoverDataIfNeeded();
+ return null;
+ }
+
+ @Override
+ public void issueCoverUpdate(ForgeDirection side) {
+ if (!mConstructed) {
+ // Queue these up and send them with the description packet
+ super.issueCoverUpdate(side);
+ } else {
+ // Otherwise, send the data right away
+ final CoverInfo coverInfo = getCoverInfoAtSide(side);
+ NW.sendPacketToAllPlayersInRange(worldObj, new GT_Packet_SendCoverData(coverInfo, this), xCoord, zCoord);
+
+ // Just in case
+ coverInfo.setNeedsUpdate(false);
+ }
+ }
+
+ @Override
+ public void receiveCoverData(ForgeDirection coverSide, int aCoverID, ISerializableObject aCoverData,
+ EntityPlayerMP aPlayer) {
+ super.receiveCoverData(coverSide, aCoverID, aCoverData, aPlayer);
+ // We don't get ticked so issue the texture update right away
+ issueTextureUpdate();
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/base/TickableMultiTileEntity.java b/src/main/java/gregtech/api/multitileentity/base/TickableMultiTileEntity.java
new file mode 100644
index 0000000000..b25504dc6a
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/base/TickableMultiTileEntity.java
@@ -0,0 +1,173 @@
+package gregtech.api.multitileentity.base;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.block.Block;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_OnNeighborBlockChange;
+import gregtech.api.task.TaskHost;
+import gregtech.api.task.TickableTask;
+import gregtech.api.util.GT_Log;
+import gregtech.api.util.GT_Util;
+
+public abstract class TickableMultiTileEntity extends MultiTileEntity implements TaskHost, IMTE_OnNeighborBlockChange {
+
+ /** Variable for seeing if the Tick Function is called right now. */
+ public boolean isRunningTick = false;
+ /** Gets set to true when the Block received a Block Update. */
+ public boolean blockUpdated = false;
+ /** Timer Value */
+ protected long timer = 0;
+ /** Variable for updating Data to the Client */
+ private boolean sendClientData = false;
+
+ private final Map<String, TickableTask<?>> tasks = new HashMap<>();
+
+ public TickableMultiTileEntity() {
+ super(true);
+ }
+
+ @Override
+ public final void registerTask(@Nonnull TickableTask<?> task) {
+ if (tasks.containsKey(task.getName())) {
+ throw new IllegalStateException(String.format("Task with name %s is already registered", task.getName()));
+ }
+ tasks.put(task.getName(), task);
+ }
+
+ @Nullable
+ public TickableTask<?> getTask(@Nonnull String name) {
+ return tasks.get(name);
+ }
+
+ @Override
+ public final void updateEntity() {
+ isRunningTick = true;
+ final boolean isServerSide = isServerSide();
+ try {
+ if (timer++ == 0) {
+ markDirty();
+ GT_Util.markChunkDirty(this);
+ onFirstTick(isServerSide);
+ }
+ if (isDead()) {
+ return;
+ }
+ onPreTick(timer, isServerSide);
+ super.updateEntity();
+ if (!isServerSide && needsUpdate) {
+ worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
+ needsUpdate = false;
+ }
+ onTick(timer, isServerSide);
+ for (TickableTask<?> task : tasks.values()) {
+ task.update(timer, isServerSide);
+ }
+ if (isServerSide && timer > 2 && sendClientData) {
+ sendClientData(null);
+ }
+ onPostTick(timer, isServerSide);
+
+ } catch (Throwable e) {
+ GT_FML_LOGGER.error("UpdateEntity Failed", e);
+ e.printStackTrace(GT_Log.err);
+ try {
+ onTickFailed(timer, isServerSide);
+ } catch (Throwable e2) {
+ GT_FML_LOGGER.error("UpdateEntity:onTickFailed Failed", e);
+ }
+ }
+
+ isRunningTick = false;
+ }
+
+ @Override
+ public void sendClientData(EntityPlayerMP aPlayer) {
+ if (sendClientData) {
+ // GT_FML_LOGGER.info("Sending client data");
+ super.sendClientData(aPlayer);
+ sendClientData = false;
+ }
+ }
+
+ /**
+ * The very first Tick happening to this TileEntity.
+ */
+ public void onFirstTick(boolean isServerSide) {
+ if (isServerSide) {
+ checkDropCover();
+ } else {
+ requestCoverDataIfNeeded();
+ }
+ }
+
+ /**
+ * The first part of the Tick, before block update.
+ */
+ public void onPreTick(long tick, boolean isServerSide) {}
+
+ /**
+ * The regular Tick. After block update, before sending data to client.
+ */
+ public void onTick(long tick, boolean isServerSide) {}
+
+ /**
+ * The absolute last part of the Tick, after sending data to client.
+ */
+ public void onPostTick(long tick, boolean isServerSide) {}
+
+ /**
+ * Gets called when there is an Exception/Error happening during one of the Tick methods.
+ */
+ public void onTickFailed(long tick, boolean isServerSide) {}
+
+ @Override
+ protected final void readTasksNBT(NBTTagCompound nbt) {
+ if (nbt.hasKey(GT_Values.NBT.TASKS)) {
+ NBTTagCompound tasksTag = nbt.getCompoundTag(GT_Values.NBT.TASKS);
+ for (TickableTask<?> task : tasks.values()) {
+ if (tasksTag.hasKey(task.getName())) {
+ task.readFromNBT(tasksTag.getCompoundTag(task.getName()));
+ }
+ }
+ }
+ }
+
+ @Override
+ protected final void writeTasksNBT(NBTTagCompound aNBT) {
+ NBTTagCompound tasksTag = new NBTTagCompound();
+ for (TickableTask<?> task : tasks.values()) {
+ NBTTagCompound tag = new NBTTagCompound();
+ task.writeToNBT(tag);
+ tasksTag.setTag(task.getName(), tag);
+ }
+ aNBT.setTag(GT_Values.NBT.TASKS, tasksTag);
+ }
+
+ @Override
+ public void onNeighborBlockChange(World aWorld, Block aBlock) {
+ blockUpdated = true;
+ }
+
+ @Override
+ public void issueClientUpdate() {
+ sendClientData = true;
+ sendGraphicPacket();
+ }
+
+ @Override
+ public byte getComparatorValue(ForgeDirection side) {
+ return 0;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileCasing.java b/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileCasing.java
new file mode 100644
index 0000000000..73bd55738a
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileCasing.java
@@ -0,0 +1,43 @@
+package gregtech.api.multitileentity.enums;
+
+import static gregtech.api.util.GT_StructureUtilityMuTE.createMuTEStructureCasing;
+import static gregtech.loaders.preload.GT_Loader_MultiTileEntities.CASING_REGISTRY_NAME;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.util.GT_StructureUtilityMuTE;
+
+public enum GT_MultiTileCasing {
+
+ CokeOven(0),
+ Chemical(1),
+ Distillation(2),
+ Macerator(18000),
+ LaserEngraver(4),
+ Mirror(5),
+ BlackLaserEngraverCasing(6),
+ LaserEngraverUpgrade1(7),
+ LaserEngraverUpgrade2(8),
+ LaserEngraverUpgrade3(9),
+ LaserEngraverUpgrade4(10),
+ NONE(GT_Values.W);
+
+ private final int meta;
+ private final GT_StructureUtilityMuTE.MuTEStructureCasing casing;
+
+ GT_MultiTileCasing(int meta) {
+ this.meta = meta;
+ casing = createMuTEStructureCasing(CASING_REGISTRY_NAME, meta);
+ }
+
+ public int getId() {
+ return meta;
+ }
+
+ public short getRegistryId() {
+ return (short) casing.getRegistryId();
+ }
+
+ public GT_StructureUtilityMuTE.MuTEStructureCasing getCasing() {
+ return casing;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileComponentCasing.java b/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileComponentCasing.java
new file mode 100644
index 0000000000..e062ecc705
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileComponentCasing.java
@@ -0,0 +1,130 @@
+package gregtech.api.multitileentity.enums;
+
+import gregtech.api.enums.GT_Values;
+
+public enum GT_MultiTileComponentCasing {
+
+ LV_Motor(0),
+ MV_Motor(1),
+ HV_Motor(2),
+ EV_Motor(3),
+ IV_Motor(4),
+ LuV_Motor(5),
+ ZPM_Motor(6),
+ UV_Motor(7),
+ UHV_Motor(8),
+ UEV_Motor(9),
+ UIV_Motor(10),
+ UMV_Motor(11),
+ UXV_Motor(12),
+ MAX_Motor(13),
+ LV_Pump(14),
+ MV_Pump(15),
+ HV_Pump(16),
+ EV_Pump(17),
+ IV_Pump(18),
+ LuV_Pump(19),
+ ZPM_Pump(20),
+ UV_Pump(21),
+ UHV_Pump(22),
+ UEV_Pump(23),
+ UIV_Pump(24),
+ UMV_Pump(25),
+ UXV_Pump(26),
+ MAX_Pump(27),
+ LV_Conveyor(28),
+ MV_Conveyor(29),
+ HV_Conveyor(30),
+ EV_Conveyor(31),
+ IV_Conveyor(32),
+ LuV_Conveyor(33),
+ ZPM_Conveyor(34),
+ UV_Conveyor(35),
+ UHV_Conveyor(36),
+ UEV_Conveyor(37),
+ UIV_Conveyor(38),
+ UMV_Conveyor(39),
+ UXV_Conveyor(40),
+ MAX_Conveyor(41),
+ LV_Piston(42),
+ MV_Piston(43),
+ HV_Piston(44),
+ EV_Piston(45),
+ IV_Piston(46),
+ LuV_Piston(47),
+ ZPM_Piston(48),
+ UV_Piston(49),
+ UHV_Piston(50),
+ UEV_Piston(51),
+ UIV_Piston(52),
+ UMV_Piston(53),
+ UXV_Piston(54),
+ MAX_Piston(55),
+ LV_RobotArm(56),
+ MV_RobotArm(57),
+ HV_RobotArm(58),
+ EV_RobotArm(59),
+ IV_RobotArm(60),
+ LuV_RobotArm(61),
+ ZPM_RobotArm(62),
+ UV_RobotArm(63),
+ UHV_RobotArm(64),
+ UEV_RobotArm(65),
+ UIV_RobotArm(66),
+ UMV_RobotArm(67),
+ UXV_RobotArm(68),
+ MAX_RobotArm(69),
+ LV_Emitter(70),
+ MV_Emitter(71),
+ HV_Emitter(72),
+ EV_Emitter(73),
+ IV_Emitter(74),
+ LuV_Emitter(75),
+ ZPM_Emitter(76),
+ UV_Emitter(77),
+ UHV_Emitter(78),
+ UEV_Emitter(79),
+ UIV_Emitter(80),
+ UMV_Emitter(81),
+ UXV_Emitter(82),
+ MAX_Emitter(83),
+ LV_Sensor(84),
+ MV_Sensor(85),
+ HV_Sensor(86),
+ EV_Sensor(87),
+ IV_Sensor(88),
+ LuV_Sensor(89),
+ ZPM_Sensor(90),
+ UV_Sensor(91),
+ UHV_Sensor(92),
+ UEV_Sensor(93),
+ UIV_Sensor(94),
+ UMV_Sensor(95),
+ UXV_Sensor(96),
+ MAX_Sensor(97),
+ LV_FieldGenerator(98),
+ MV_FieldGenerator(99),
+ HV_FieldGenerator(100),
+ EV_FieldGenerator(101),
+ IV_FieldGenerator(102),
+ LuV_FieldGenerator(103),
+ ZPM_FieldGenerator(104),
+ UV_FieldGenerator(105),
+ UHV_FieldGenerator(106),
+ UEV_FieldGenerator(107),
+ UIV_FieldGenerator(108),
+ UMV_FieldGenerator(109),
+ UXV_FieldGenerator(110),
+ MAX_FieldGenerator(111),
+ NONE(GT_Values.W);
+
+ private final int meta;
+
+ GT_MultiTileComponentCasing(int meta) {
+ this.meta = meta;
+ }
+
+ public int getId() {
+ return meta;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileMachine.java b/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileMachine.java
new file mode 100644
index 0000000000..7cdde78986
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileMachine.java
@@ -0,0 +1,19 @@
+package gregtech.api.multitileentity.enums;
+
+import gregtech.api.enums.GT_Values;
+
+public enum GT_MultiTileMachine {
+
+ CokeOven(0),
+ NONE(GT_Values.W);
+
+ private final int meta;
+
+ GT_MultiTileMachine(int meta) {
+ this.meta = meta;
+ }
+
+ public int getId() {
+ return meta;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileUpgradeCasing.java b/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileUpgradeCasing.java
new file mode 100644
index 0000000000..296bae546d
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/enums/GT_MultiTileUpgradeCasing.java
@@ -0,0 +1,71 @@
+package gregtech.api.multitileentity.enums;
+
+import gregtech.api.enums.GT_Values;
+
+public enum GT_MultiTileUpgradeCasing {
+
+ ULV_Inventory(0),
+ LV_Inventory(1),
+ MV_Inventory(2),
+ HV_Inventory(3),
+ EV_Inventory(4),
+ IV_Inventory(5),
+ LuV_Inventory(6),
+ ZPM_Inventory(7),
+ UV_Inventory(8),
+ UHV_Inventory(9),
+ UEV_Inventory(10),
+ UIV_Inventory(11),
+ UXV_Inventory(12),
+ UMV_Inventory(13),
+ MAX_Inventory(14),
+ ULV_Tank(15),
+ LV_Tank(16),
+ MV_Tank(17),
+ HV_Tank(18),
+ EV_Tank(19),
+ IV_Tank(20),
+ LuV_Tank(21),
+ ZPM_Tank(22),
+ UV_Tank(23),
+ UHV_Tank(24),
+ UEV_Tank(25),
+ UIV_Tank(26),
+ UXV_Tank(27),
+ UMV_Tank(28),
+ MAX_Tank(29),
+ Amp_4(30),
+ Amp_16(31),
+ Amp_64(32),
+ Amp_256(33),
+ Amp_1_024(34),
+ Amp_4_096(35),
+ Amp_16_384(36),
+ Amp_65_536(37),
+ Amp_262_144(38),
+ Amp_1_048_576(39),
+ Laser(40),
+ Wireless(41),
+ Cleanroom(42),
+ Heater_Prototype(100),
+ Heater_IndustrialGrade(101),
+ Heater_NextGen(102),
+ Heater_Omnipotent(103),
+ Heater_OmegaType(104),
+ Insulator_Prototype(105),
+ Insulator_IndustrialGrade(106),
+ Insulator_NextGen(107),
+ Insulator_Omnipotent(108),
+ Insulator_OmegaType(109),
+ NONE(GT_Values.W);
+
+ private final int meta;
+
+ GT_MultiTileUpgradeCasing(int meta) {
+ this.meta = meta;
+ }
+
+ public int getId() {
+ return meta;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/enums/MultiTileCasingPurpose.java b/src/main/java/gregtech/api/multitileentity/enums/MultiTileCasingPurpose.java
new file mode 100644
index 0000000000..2733da33cf
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/enums/MultiTileCasingPurpose.java
@@ -0,0 +1,13 @@
+package gregtech.api.multitileentity.enums;
+
+/**
+ * Purposes with which a casing can registered itself in the MuTE controller to be ticked.
+ * Can be used for example to auto output recipe outputs from output casings.
+ *
+ * @author minecraft7771
+ */
+public enum MultiTileCasingPurpose {
+ ItemOutput,
+ FluidOutput,
+ EnergyOutput,
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/IItemUpdatable.java b/src/main/java/gregtech/api/multitileentity/interfaces/IItemUpdatable.java
new file mode 100644
index 0000000000..89d281eb27
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IItemUpdatable.java
@@ -0,0 +1,19 @@
+package gregtech.api.multitileentity.interfaces;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+
+public interface IItemUpdatable {
+
+ /**
+ * Updates the Data of the ItemStack. Not called every tick but instead called whenever something important happens
+ * to the Stack.
+ */
+ void updateItemStack(ItemStack aStack);
+
+ /**
+ * Updates the Data of the ItemStack. Not called every tick but instead called whenever something important happens
+ * to the Stack.
+ */
+ void updateItemStack(ItemStack aStack, World aWorld, int aX, int aY, int aZ);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockController.java b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockController.java
new file mode 100644
index 0000000000..58af918c50
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockController.java
@@ -0,0 +1,51 @@
+package gregtech.api.multitileentity.interfaces;
+
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.util.ChunkCoordinates;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import gregtech.api.enums.InventoryType;
+import gregtech.api.gui.GUIHost;
+import gregtech.api.logic.FluidInventoryLogic;
+import gregtech.api.logic.ItemInventoryLogic;
+import gregtech.api.logic.interfaces.FluidInventoryLogicHost;
+import gregtech.api.logic.interfaces.ItemInventoryLogicHost;
+import gregtech.api.logic.interfaces.PowerLogicHost;
+import gregtech.api.multitileentity.enums.MultiTileCasingPurpose;
+
+public interface IMultiBlockController
+ extends IMultiTileEntity, FluidInventoryLogicHost, ItemInventoryLogicHost, UpgradableMuTE, PowerLogicHost, GUIHost {
+
+ boolean checkStructure(boolean aForceReset);
+
+ /** Set the structure as having changed, and trigger an update */
+ void onStructureChange();
+
+ @Override
+ ChunkCoordinates getCoords();
+
+ void registerCoveredPartOnSide(final ForgeDirection side, IMultiBlockPart part);
+
+ void unregisterCoveredPartOnSide(final ForgeDirection side, IMultiBlockPart part);
+
+ void registerCaseWithPurpose(MultiTileCasingPurpose purpose, IMultiBlockPart part);
+
+ void unregisterCaseWithPurpose(MultiTileCasingPurpose purpose, IMultiBlockPart part);
+
+ UUID registerItemInventory(int slots, int tier, @Nonnull InventoryType type, boolean isUpgradeInventory);
+
+ ItemInventoryLogic unregisterItemInventory(@Nonnull UUID id, @Nonnull InventoryType type);
+
+ void changeItemInventoryDisplayName(@Nonnull UUID id, @Nullable String displayName, @Nonnull InventoryType type);
+
+ UUID registerFluidInventory(int tanks, long capacity, int tier, @Nonnull InventoryType type,
+ boolean isUpgradeInventory);
+
+ FluidInventoryLogic unregisterFluidInventory(@Nonnull UUID id, @Nonnull InventoryType type);
+
+ void changeFluidInventoryDisplayName(@Nonnull UUID id, @Nullable String displayName, @Nonnull InventoryType type);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockEnergy.java b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockEnergy.java
new file mode 100644
index 0000000000..d6d8bf5310
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockEnergy.java
@@ -0,0 +1,42 @@
+package gregtech.api.multitileentity.interfaces;
+
+import net.minecraftforge.common.util.ForgeDirection;
+
+import gregtech.api.multitileentity.multiblock.base.MultiBlockPart;
+
+interface IMultiBlockEnergy {
+
+ boolean isUniversalEnergyStored(MultiBlockPart aPart, long aEnergyAmount);
+
+ long getUniversalEnergyStored(MultiBlockPart aPart);
+
+ long getUniversalEnergyCapacity(MultiBlockPart aPart);
+
+ long getOutputAmperage(MultiBlockPart aPart);
+
+ long getOutputVoltage(MultiBlockPart aPart);
+
+ long getInputAmperage(MultiBlockPart aPart);
+
+ long getInputVoltage(MultiBlockPart aPart);
+
+ boolean decreaseStoredEnergyUnits(MultiBlockPart aPart, long aEnergy, boolean aIgnoreTooLittleEnergy);
+
+ boolean increaseStoredEnergyUnits(MultiBlockPart aPart, long aEnergy, boolean aIgnoreTooMuchEnergy);
+
+ boolean drainEnergyUnits(MultiBlockPart aPart, ForgeDirection side, long aVoltage, long aAmperage);
+
+ long injectEnergyUnits(MultiBlockPart aPart, ForgeDirection side, long aVoltage, long aAmperage);
+
+ long getAverageElectricInput(MultiBlockPart aPart);
+
+ long getAverageElectricOutput(MultiBlockPart aPart);
+
+ long getStoredEU(MultiBlockPart aPart);
+
+ long getEUCapacity(MultiBlockPart aPart);
+
+ boolean inputEnergyFrom(MultiBlockPart aPart, ForgeDirection side);
+
+ boolean outputsEnergyTo(MultiBlockPart aPart, ForgeDirection side);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockFluidHandler.java b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockFluidHandler.java
new file mode 100644
index 0000000000..b513f51324
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockFluidHandler.java
@@ -0,0 +1,32 @@
+package gregtech.api.multitileentity.interfaces;
+
+import java.util.List;
+
+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 gregtech.api.multitileentity.multiblock.base.MultiBlockPart;
+
+public interface IMultiBlockFluidHandler {
+
+ int fill(MultiBlockPart aPart, ForgeDirection aDirection, FluidStack aFluid, boolean aDoFill);
+
+ FluidStack drain(MultiBlockPart aPart, ForgeDirection aDirection, FluidStack aFluid, boolean aDoDrain);
+
+ FluidStack drain(MultiBlockPart aPart, ForgeDirection aDirection, int aAmountToDrain, boolean aDoDrain);
+
+ boolean canFill(MultiBlockPart aPart, ForgeDirection aDirection, Fluid aFluid);
+
+ boolean canDrain(MultiBlockPart aPart, ForgeDirection aDirection, Fluid aFluid);
+
+ FluidTankInfo[] getTankInfo(MultiBlockPart aPart, ForgeDirection aDirection);
+
+ IFluidTank[] getFluidTanksForGUI(MultiBlockPart aPart);
+
+ List<String> getTankArrayNames(MultiBlockPart aPart);
+
+ List<String> getTankArrayIDs(MultiBlockPart aPart);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockInventory.java b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockInventory.java
new file mode 100644
index 0000000000..5dc0fec196
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockInventory.java
@@ -0,0 +1,60 @@
+package gregtech.api.multitileentity.interfaces;
+
+import java.util.List;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
+
+import gregtech.api.multitileentity.multiblock.base.MultiBlockPart;
+
+public interface IMultiBlockInventory {
+
+ boolean hasInventoryBeenModified(MultiBlockPart aPart);
+
+ boolean isValidSlot(MultiBlockPart aPart, int aIndex);
+
+ boolean addStackToSlot(MultiBlockPart aPart, int aIndex, ItemStack aStack);
+
+ boolean addStackToSlot(MultiBlockPart aPart, int aIndex, ItemStack aStack, int aAmount);
+
+ int[] getAccessibleSlotsFromSide(MultiBlockPart aPart, ForgeDirection side);
+
+ boolean canInsertItem(MultiBlockPart aPart, int aSlot, ItemStack aStack, ForgeDirection side);
+
+ boolean canExtractItem(MultiBlockPart aPart, int aSlot, ItemStack aStack, ForgeDirection side);
+
+ int getSizeInventory(MultiBlockPart aPart);
+
+ ItemStack getStackInSlot(MultiBlockPart aPart, int aSlot);
+
+ ItemStack decrStackSize(MultiBlockPart aPart, int aSlot, int aDecrement);
+
+ ItemStack getStackInSlotOnClosing(MultiBlockPart aPart, int aSlot);
+
+ void setInventorySlotContents(MultiBlockPart aPart, int aSlot, ItemStack aStack);
+
+ String getInventoryName(MultiBlockPart aPart);
+
+ boolean hasCustomInventoryName(MultiBlockPart aPart);
+
+ int getInventoryStackLimit(MultiBlockPart aPart);
+
+ void markDirty(MultiBlockPart aPart);
+
+ boolean isUseableByPlayer(MultiBlockPart aPart, EntityPlayer aPlayer);
+
+ void openInventory(MultiBlockPart aPart);
+
+ void closeInventory(MultiBlockPart aPart);
+
+ boolean isItemValidForSlot(MultiBlockPart aPart, int aSlot, ItemStack aStack);
+
+ IItemHandlerModifiable getInventoryForGUI(MultiBlockPart aPart);
+
+ List<String> getInventoryNames(MultiBlockPart aPart);
+
+ List<String> getInventoryIDs(MultiBlockPart aPart);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockPart.java b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockPart.java
new file mode 100644
index 0000000000..59d838fdeb
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockPart.java
@@ -0,0 +1,26 @@
+package gregtech.api.multitileentity.interfaces;
+
+import java.util.UUID;
+
+import net.minecraft.util.ChunkCoordinates;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import gregtech.api.logic.interfaces.FluidInventoryLogicHost;
+import gregtech.api.logic.interfaces.ItemInventoryLogicHost;
+
+public interface IMultiBlockPart extends IMultiTileEntity, ItemInventoryLogicHost, FluidInventoryLogicHost {
+
+ ChunkCoordinates getTargetPos();
+
+ void setTargetPos(ChunkCoordinates aTargetPos);
+
+ void setLockedInventoryIndex(int aIndex);
+
+ int getLockedInventoryIndex();
+
+ UUID getLockedInventory();
+
+ boolean tickCoverAtSide(ForgeDirection side, long aTickTimer);
+
+ boolean shouldTick(long tickTimer);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/IMultiTileEntity.java b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiTileEntity.java
new file mode 100644
index 0000000000..91803690fc
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiTileEntity.java
@@ -0,0 +1,293 @@
+package gregtech.api.multitileentity.interfaces;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import net.minecraft.block.Block;
+import net.minecraft.creativetab.CreativeTabs;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.MovingObjectPosition;
+import net.minecraft.world.Explosion;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import cpw.mods.fml.common.Optional;
+import gregtech.api.enums.Mods;
+import gregtech.api.interfaces.tileentity.ICoverable;
+import gregtech.api.interfaces.tileentity.IDebugableTileEntity;
+import gregtech.api.interfaces.tileentity.ITurnable;
+import gregtech.api.multitileentity.MultiTileEntityBlockInternal;
+import gregtech.api.multitileentity.MultiTileEntityItemInternal;
+import gregtech.api.multitileentity.MultiTileEntityRegistry;
+
+/*
+ * Heavily inspired by GT6
+ */
+public interface IMultiTileEntity extends ICoverable, ITurnable, IDebugableTileEntity {
+
+ /**
+ * Those two IDs HAVE to be saved inside the NBT of the TileEntity itself. They get set by the Registry itself, when
+ * the TileEntity is placed.
+ */
+ short getMultiTileEntityID();
+
+ short getMultiTileEntityRegistryID();
+
+ /**
+ * Called by the Registry with the default NBT Parameters and the two IDs you have to save, when the TileEntity is
+ * created. aNBT may be null, take that into account if you decide to call the regular readFromNBT Function from
+ * here.
+ */
+ void initFromNBT(NBTTagCompound aNBT, short aMTEID, short aMTERegistry);
+
+ /** Writes Item Data to the NBT. */
+ NBTTagCompound writeItemNBT(NBTTagCompound aNBT);
+
+ /** Sets the Item Display Name. Use null to reset it. */
+ void setCustomName(String aName);
+
+ String getCustomName();
+
+ /** return the internal Name of this TileEntity to be registered. */
+ String getTileEntityName();
+
+ /**
+ * Called when a TileEntity of this particular Class is being registered first at any MultiTileEntity Registry. So
+ * basically one call per Class.
+ */
+ void onRegistrationFirst(MultiTileEntityRegistry aRegistry, short aID);
+
+ /** Called after the TileEntity has been placed and set up. */
+ void onTileEntityPlaced();
+
+ /** Checks if the TileEntity is Invalid or Unloaded, should bes required for every TileEntity. */
+ @Override
+ boolean isDead();
+
+ void loadTextures(String folder);
+
+ void copyTextures();
+
+ void issueClientUpdate();
+
+ void sendClientData(EntityPlayerMP aPlayer);
+
+ boolean receiveClientData(int aEventID, int aValue);
+
+ void setShouldRefresh(boolean aShouldRefresh);
+
+ void addCollisionBoxesToList(AxisAlignedBB aAABB, List<AxisAlignedBB> aList, Entity aEntity);
+
+ AxisAlignedBB getCollisionBoundingBoxFromPool();
+
+ AxisAlignedBB getSelectedBoundingBoxFromPool();
+
+ void setBlockBoundsBasedOnState(Block aBlock);
+
+ void onBlockAdded();
+
+ boolean playerOwnsThis(EntityPlayer aPlayer, boolean aCheckPrecicely);
+
+ boolean privateAccess();
+
+ /** @return the amount of Time this TileEntity has been loaded. */
+ @Override
+ long getTimer();
+
+ /** Sets the Owner of the Machine. Returns the set Name. */
+ String setOwnerName(String aName);
+
+ /** gets the Name of the Machines Owner or "Player" if not set. */
+ String getOwnerName();
+
+ /** Gets the UniqueID of the Machines Owner. */
+ UUID getOwnerUuid();
+
+ /** Sets the UniqueID of the Machines Owner. */
+ void setOwnerUuid(UUID uuid);
+
+ /**
+ * Causes a general Texture update. Only used Client Side to mark Blocks dirty.
+ */
+ void issueTextureUpdate();
+
+ /**
+ * Paintable Support
+ */
+ boolean unpaint();
+
+ boolean isPainted();
+
+ boolean paint(int aRGB);
+
+ int getPaint();
+
+ /**
+ * Sets the main facing to {side} and update as appropriately
+ *
+ * @return Whether the facing was changed
+ */
+ boolean setMainFacing(ForgeDirection side);
+
+ boolean isFacingValid(ForgeDirection facing);
+
+ void onFacingChange();
+
+ @Override
+ default void setFrontFacing(ForgeDirection side) {
+ setMainFacing(side);
+ }
+
+ boolean shouldTriggerBlockUpdate();
+
+ void onMachineBlockUpdate();
+
+ boolean allowInteraction(Entity aEntity);
+
+ default void onLeftClick(EntityPlayer aPlayer) {
+ /* do nothing */
+ }
+
+ boolean onBlockActivated(EntityPlayer aPlayer, ForgeDirection side, float aX, float aY, float aZ);
+
+ boolean onRightClick(EntityPlayer aPlayer, ForgeDirection side, float aX, float aY, float aZ);
+
+ ArrayList<ItemStack> getDrops(int aFortune, boolean aSilkTouch);
+
+ boolean isSideSolid(ForgeDirection side);
+
+ float getExplosionResistance(Entity aExploder, double aExplosionX, double aExplosionY, double aExplosionZ);
+
+ float getExplosionResistance();
+
+ void onExploded(Explosion aExplosion);
+
+ boolean recolourBlock(ForgeDirection side, byte aColor);
+
+ /** Adds to the Creative Tab. return false to prevent it from being added. */
+ boolean getSubItems(MultiTileEntityBlockInternal aBlock, Item aItem, CreativeTabs aTab, List<ItemStack> aList,
+ short aID);
+
+ ItemStack getPickBlock(MovingObjectPosition aTarget);
+
+ boolean shouldSideBeRendered(ForgeDirection side);
+
+ boolean isSurfaceOpaque(ForgeDirection side);
+
+ boolean onPlaced(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, ForgeDirection side,
+ float aHitX, float aHitY, float aHitZ);
+
+ // ItemStack getPickBlock(MovingObjectPosition aTarget);
+
+ /*
+ * Various Sub Interfaces from GT6
+ */
+
+ interface IMTE_OnNeighborBlockChange extends IMultiTileEntity {
+
+ void onNeighborBlockChange(World aWorld, Block aBlock);
+ }
+
+ interface IMTE_IsProvidingWeakPower extends IMultiTileEntity {
+
+ /** Remember that it passes the opposite Side due to the way vanilla works! */
+ int isProvidingWeakPower(ForgeDirection oppositeSide);
+ }
+
+ interface IMTE_IsProvidingStrongPower extends IMultiTileEntity {
+
+ /** Remember that it passes the opposite Side due to the way vanilla works! */
+ int isProvidingStrongPower(ForgeDirection oppositeSide);
+ }
+
+ interface IMTE_ShouldCheckWeakPower extends IMultiTileEntity {
+
+ boolean shouldCheckWeakPower(ForgeDirection side);
+ }
+
+ interface IMTE_GetWeakChanges extends IMultiTileEntity {
+
+ boolean getWeakChanges();
+ }
+
+ interface IMTE_GetComparatorInputOverride extends IMultiTileEntity {
+
+ int getComparatorInputOverride(ForgeDirection side);
+ }
+
+ interface IMTE_BreakBlock extends IMultiTileEntity {
+
+ /** return true to prevent the TileEntity from being removed. */
+ boolean breakBlock();
+ }
+
+ interface IMTE_HasMultiBlockMachineRelevantData extends IMultiTileEntity {
+
+ /** Return true to mark this Block as a Machine Block for Multiblocks. (Triggers machine update thread) */
+ boolean hasMultiBlockMachineRelevantData();
+ }
+
+ interface IMTE_GetBlockHardness extends IMultiTileEntity {
+
+ float getBlockHardness();
+ }
+
+ interface IMTE_GetFoodValues extends IMultiTileEntity {
+
+ @Optional.Method(modid = Mods.Names.APPLE_CORE)
+ squeek.applecore.api.food.FoodValues getFoodValues(MultiTileEntityItemInternal aItem, ItemStack aStack);
+ }
+
+ interface IMTE_OnlyPlaceableWhenSneaking extends IMultiTileEntity {
+
+ /** Return true to prevent placing this Block without Sneaking. */
+ boolean onlyPlaceableWhenSneaking();
+ }
+
+ interface IMTE_IgnoreEntityCollisionWhenPlacing extends IMultiTileEntity {
+
+ /**
+ * Return true to ignore the Player standing in the way of placing this Block; useful for things like
+ * pipes/wires.
+ */
+ boolean ignoreEntityCollisionWhenPlacing(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY,
+ int aZ, ForgeDirection side, float aHitX, float aHitY, float aHitZ);
+ }
+
+ interface IMTE_CanPlace extends IMultiTileEntity {
+
+ /** Return false if this TileEntity cannot be placed at that Location. */
+ boolean canPlace(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ,
+ ForgeDirection side, float aHitX, float aHitY, float aHitZ);
+ }
+
+ interface IMTE_GetMaxStackSize extends IMultiTileEntity {
+
+ /** Gets the Max Stacksize of this Item. */
+ byte getMaxStackSize(ItemStack aStack, byte aDefault);
+ }
+
+ interface IMTE_AddToolTips extends IMultiTileEntity {
+
+ /** Adds ToolTips to the Item. */
+ void addToolTips(List<String> aList, ItemStack aStack, boolean aF3_H);
+ }
+
+ interface IMTE_HasModes extends IMultiTileEntity {
+
+ int getMode();
+
+ void setMode(int mode);
+
+ int getAllowedModes();
+
+ void setAllowedModes(int allowedModes);
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/IMultiTileMachine.java b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiTileMachine.java
new file mode 100644
index 0000000000..babb85d118
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiTileMachine.java
@@ -0,0 +1,10 @@
+package gregtech.api.multitileentity.interfaces;
+
+public interface IMultiTileMachine {
+
+ void setBooleans(int booleans);
+
+ int getBooleans();
+
+ void setSound(byte soundEvent, int soundEventValue);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/SyncedMultiTileEntity.java b/src/main/java/gregtech/api/multitileentity/interfaces/SyncedMultiTileEntity.java
new file mode 100644
index 0000000000..2045f28d67
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/SyncedMultiTileEntity.java
@@ -0,0 +1,63 @@
+package gregtech.api.multitileentity.interfaces;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.entity.player.EntityPlayerMP;
+
+import gregtech.api.net.GT_Packet_MultiTileEntity;
+
+public interface SyncedMultiTileEntity {
+
+ public static final int DEFAULT_TIMED_PACKET_PERIOD = 20;
+
+ /**
+ * Will send a packet to the client when they open the controller or access a casing.
+ * Should only be sent to one player!
+ */
+ void sendFullPacket(@Nonnull EntityPlayerMP player);
+
+ /**
+ * Should always collect all the data that the controller or casing has and should send
+ * Called by {@link #sendFullPacket()}
+ *
+ * @param packet The packet which will be sent
+ */
+ void getFullPacketData(GT_Packet_MultiTileEntity packet);
+
+ /**
+ * Will send a packet at a certain period of time, defined by {@link #getTimedPacketPeriod()}, to all players around
+ * the controller or casing to send important information.
+ * Redstone state, color, ect. It shouldn't send data about the internals like inventory and processing time
+ */
+ void sendTimedPacket();
+
+ /**
+ * Collects all the data that should be sent out at a certain period of time defined by
+ * {@link #getTimedPacketPeriod()}
+ * Called by {@link #sendTimedPacket()}
+ *
+ * @param packet The packet which will be sent
+ */
+ void getTimedPacketData(GT_Packet_MultiTileEntity packet);
+
+ /**
+ * Defines the period of time at which a timed packet should be sent out. Default 20 ticks
+ */
+ default int getTimedPacketPeriod() {
+ return DEFAULT_TIMED_PACKET_PERIOD;
+ }
+
+ /**
+ * Will send a packet, which should only contain data about how the TileEntity should be rendered.
+ * !!! Warning !!! This is sent every single tick! Do not put a lot of data here!
+ */
+ void sendGraphicPacket();
+
+ /**
+ * Collects all the data that is needed to be send every single tick
+ * Called by {@link #sendGraphicPacket()}
+ *
+ * @param packet The packet which will be sent
+ */
+ void getGraphicPacketData(GT_Packet_MultiTileEntity packet);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/UpgradableModularMuTE.java b/src/main/java/gregtech/api/multitileentity/interfaces/UpgradableModularMuTE.java
new file mode 100644
index 0000000000..3b4c3cb6f3
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/UpgradableModularMuTE.java
@@ -0,0 +1,10 @@
+package gregtech.api.multitileentity.interfaces;
+
+import gregtech.api.util.GT_StructureUtilityMuTE.UpgradeCasings;
+
+public interface UpgradableModularMuTE extends UpgradableMuTE {
+
+ void increaseMucCount(UpgradeCasings casingType, int tier);
+
+ void resetMucCount();
+}
diff --git a/src/main/java/gregtech/api/multitileentity/interfaces/UpgradableMuTE.java b/src/main/java/gregtech/api/multitileentity/interfaces/UpgradableMuTE.java
new file mode 100644
index 0000000000..c18852c95e
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/UpgradableMuTE.java
@@ -0,0 +1,12 @@
+package gregtech.api.multitileentity.interfaces;
+
+public interface UpgradableMuTE {
+
+ void setCleanroom(boolean isCleanroom);
+
+ void setWirelessSupport(boolean canUse);
+
+ void setLaserSupport(boolean canUse);
+
+ void setMaxAmperage(long amperage);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/machine/MultiTileBasicMachine.java b/src/main/java/gregtech/api/multitileentity/machine/MultiTileBasicMachine.java
new file mode 100644
index 0000000000..4b348c2fec
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/machine/MultiTileBasicMachine.java
@@ -0,0 +1,800 @@
+package gregtech.api.multitileentity.machine;
+
+import static gregtech.api.enums.GT_Values.*;
+import static gregtech.api.enums.TickTime.MINUTE;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.tileentity.TileEntityFurnace;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.StatCollector;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.jetbrains.annotations.ApiStatus.OverrideOnly;
+
+import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
+import com.gtnewhorizons.modularui.api.forge.ItemStackHandler;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.GT_Values.NBT;
+import gregtech.api.enums.InventoryType;
+import gregtech.api.enums.Mods;
+import gregtech.api.enums.SoundResource;
+import gregtech.api.enums.Textures;
+import gregtech.api.enums.Textures.BlockIcons.CustomIcon;
+import gregtech.api.enums.TickTime;
+import gregtech.api.enums.VoidingMode;
+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.MuTEProcessingLogic;
+import gregtech.api.logic.NullPowerLogic;
+import gregtech.api.logic.PowerLogic;
+import gregtech.api.logic.interfaces.PowerLogicHost;
+import gregtech.api.logic.interfaces.ProcessingLogicHost;
+import gregtech.api.metatileentity.GregTechTileClientEvents;
+import gregtech.api.multitileentity.MultiTileEntityRegistry;
+import gregtech.api.multitileentity.base.TickableMultiTileEntity;
+import gregtech.api.multitileentity.interfaces.IMultiTileMachine;
+import gregtech.api.render.TextureFactory;
+import gregtech.api.task.tasks.ProcessingTask;
+import gregtech.api.util.GT_Utility;
+import gregtech.client.GT_SoundLoop;
+import gregtech.common.gui.MachineGUIProvider;
+
+public abstract class MultiTileBasicMachine<P extends MuTEProcessingLogic<P>> extends TickableMultiTileEntity
+ implements IMultiTileMachine, ProcessingLogicHost<P>, PowerLogicHost, GUIHost {
+
+ protected static final int ACTIVE = B[0];
+ protected static final int TICKS_BETWEEN_RECIPE_CHECKS = 5 * TickTime.SECOND;
+ protected static final int POLLUTION_TICK = TickTime.SECOND;
+ protected static final byte INTERRUPT_SOUND_INDEX = 8;
+ protected static final byte PROCESS_START_SOUND_INDEX = 1;
+
+ protected static final IItemHandlerModifiable EMPTY_INVENTORY = new ItemStackHandler(0);
+
+ public ITexture activeOverlayTexture = null;
+ public ITexture activeOverlayGlowTexture = null;
+ public ITexture inactiveOverlayTexture = null;
+ public ITexture inactiveOverlayGlowTexture = null;
+
+ protected int maxParallel = 1;
+ protected boolean active = false;
+ protected int tier = 0;
+ protected long burnTime = 0;
+ protected long totalBurnTime = 0;
+
+ protected boolean outputInventoryChanged = false;
+ protected boolean powerShutDown = false;
+ protected boolean wasEnabled = false;
+ protected boolean canWork = true;
+ protected boolean isElectric = true;
+ protected boolean isSteam = false;
+ protected boolean acceptsFuel = false;
+
+ protected byte soundEvent = 0;
+ protected int soundEventValue = 0;
+ protected ItemInventoryLogic itemInput;
+ protected ItemInventoryLogic itemOutput;
+ protected FluidInventoryLogic fluidInput;
+ protected FluidInventoryLogic fluidOutput;
+
+ protected P processingLogic;
+ @Nonnull
+ protected VoidingMode voidingMode = VoidingMode.VOID_NONE;
+ protected boolean processingUpdate = false;
+ @Nonnull
+ protected PowerLogic power = createPowerLogic();
+ @Nonnull
+ protected GUIProvider<?> guiProvider = createGUIProvider();
+
+ @SideOnly(Side.CLIENT)
+ protected GT_SoundLoop activitySoundLoop;
+
+ public MultiTileBasicMachine() {
+ new ProcessingTask<>(this);
+ }
+
+ @Override
+ public String getTileEntityName() {
+ return "gt.multitileentity.machine.basic";
+ }
+
+ @Override
+ public void writeMultiTileNBT(NBTTagCompound nbt) {
+ super.writeMultiTileNBT(nbt);
+ if (maxParallel > 0) {
+ nbt.setInteger(NBT.PARALLEL, maxParallel);
+ }
+
+ if (active) {
+ nbt.setBoolean(NBT.ACTIVE, active);
+ }
+
+ saveItemLogic(nbt);
+ saveFluidLogic(nbt);
+
+ if (processingLogic != null) {
+ NBTTagCompound processingLogicNBT = processingLogic.saveToNBT();
+ nbt.setTag("processingLogic", processingLogicNBT);
+ }
+
+ nbt.setInteger(NBT.TIER, tier);
+ nbt.setLong(NBT.BURN_TIME_LEFT, burnTime);
+ nbt.setLong(NBT.TOTAL_BURN_TIME, totalBurnTime);
+ nbt.setBoolean(NBT.ALLOWED_WORK, canWork);
+ nbt.setBoolean(NBT.ACTIVE, active);
+ power.saveToNBT(nbt);
+ }
+
+ protected void saveItemLogic(NBTTagCompound nbt) {
+ NBTTagCompound nbtListInput = itemInput.saveToNBT();
+ nbt.setTag(NBT.INV_INPUT_LIST, nbtListInput);
+ NBTTagCompound nbtListOutput = itemOutput.saveToNBT();
+ nbt.setTag(NBT.INV_OUTPUT_LIST, nbtListOutput);
+ }
+
+ protected void saveFluidLogic(NBTTagCompound nbt) {
+ NBTTagCompound fluidInputNBT = fluidInput.saveToNBT();
+ nbt.setTag(NBT.TANK_IN, fluidInputNBT);
+ NBTTagCompound fluidOutputNBT = fluidOutput.saveToNBT();
+ nbt.setTag(NBT.TANK_OUT, fluidOutputNBT);
+ }
+
+ @Override
+ public void readMultiTileNBT(NBTTagCompound nbt) {
+ super.readMultiTileNBT(nbt);
+ if (nbt.hasKey(NBT.PARALLEL)) {
+ maxParallel = Math.max(1, nbt.getInteger(NBT.PARALLEL));
+ }
+
+ if (nbt.hasKey(NBT.ACTIVE)) {
+ active = nbt.getBoolean(NBT.ACTIVE);
+ }
+
+ loadItemLogic(nbt);
+ loadFluidLogic(nbt);
+
+ if (nbt.hasKey("processingLogic")) {
+ P processingLogic = getProcessingLogic();
+ processingLogic.loadFromNBT(Objects.requireNonNull(nbt.getCompoundTag("processingLogic")));
+ }
+
+ tier = nbt.getInteger(NBT.TIER);
+ burnTime = nbt.getLong(NBT.BURN_TIME_LEFT);
+ totalBurnTime = nbt.getLong(NBT.TOTAL_BURN_TIME);
+ canWork = nbt.getBoolean(NBT.ALLOWED_WORK);
+ active = nbt.getBoolean(NBT.ACTIVE);
+ power.loadFromNBT(nbt);
+ }
+
+ protected void loadItemLogic(NBTTagCompound nbt) {
+ itemInput = new ItemInventoryLogic(nbt.getInteger(NBT.INV_OUTPUT_SIZE), tier);
+ itemOutput = new ItemInventoryLogic(nbt.getInteger(NBT.INV_OUTPUT_SIZE), tier);
+ if (nbt.hasKey(NBT.INV_INPUT_LIST)) {
+ itemInput.loadFromNBT(nbt.getCompoundTag(NBT.INV_INPUT_LIST));
+ }
+ if (nbt.hasKey(NBT.INV_OUTPUT_LIST)) {
+ itemOutput.loadFromNBT(nbt.getCompoundTag(NBT.INV_OUTPUT_LIST));
+ }
+ }
+
+ protected void loadFluidLogic(NBTTagCompound nbt) {
+ fluidInput = new FluidInventoryLogic(16, 10000, tier);
+ fluidOutput = new FluidInventoryLogic(16, 10000, tier);
+ fluidInput.loadFromNBT(nbt.getCompoundTag(NBT.TANK_IN));
+ fluidOutput.loadFromNBT(nbt.getCompoundTag(NBT.TANK_OUT));
+ }
+
+ public boolean checkTexture(String modID, String resourcePath) {
+ try {
+ Minecraft.getMinecraft()
+ .getResourceManager()
+ .getResource(new ResourceLocation(modID, resourcePath));
+ return true;
+ } catch (IOException ignored) {
+ return false;
+ }
+ }
+
+ @Override
+ public void loadTextures(String folder) {
+ super.loadTextures(folder);
+ for (StatusTextures textureName : StatusTextures.TEXTURES) {
+ ITexture texture = null;
+ String texturePath = "textures/blocks/multitileentity/" + folder + "/" + textureName.getName() + ".png";
+ if (!checkTexture(Mods.GregTech.ID, texturePath)) {
+ texture = TextureFactory.of(Textures.BlockIcons.VOID);
+ } else {
+ if (textureName.hasGlow()) {
+ texture = TextureFactory.builder()
+ .addIcon(new CustomIcon("multitileentity/" + folder + "/" + textureName.getName()))
+ .glow()
+ .build();
+ } else {
+ texture = TextureFactory
+ .of(new CustomIcon("multitileentity/" + folder + "/" + textureName.getName()));
+ }
+ }
+ switch (textureName) {
+ case Active -> activeOverlayTexture = texture;
+ case ActiveWithGlow -> activeOverlayGlowTexture = texture;
+ case Inactive -> inactiveOverlayTexture = texture;
+ case InactiveWithGlow -> inactiveOverlayGlowTexture = texture;
+ }
+ }
+ }
+
+ @Override
+ public void copyTextures() {
+ super.copyTextures();
+ final TileEntity tCanonicalTileEntity = MultiTileEntityRegistry
+ .getCanonicalTileEntity(getMultiTileEntityRegistryID(), getMultiTileEntityID());
+ if (!(tCanonicalTileEntity instanceof MultiTileBasicMachine)) {
+ return;
+ }
+ final MultiTileBasicMachine canonicalEntity = (MultiTileBasicMachine) tCanonicalTileEntity;
+ activeOverlayTexture = canonicalEntity.activeOverlayTexture;
+ activeOverlayGlowTexture = canonicalEntity.activeOverlayGlowTexture;
+ inactiveOverlayTexture = canonicalEntity.inactiveOverlayTexture;
+ inactiveOverlayGlowTexture = canonicalEntity.inactiveOverlayGlowTexture;
+ }
+
+ @Override
+ public ITexture getTexture(ForgeDirection side) {
+ final ITexture texture = super.getTexture(side);
+ if (side == facing) {
+ if (isActive()) {
+ return TextureFactory.of(texture, activeOverlayTexture, activeOverlayGlowTexture);
+ }
+
+ return TextureFactory.of(texture, inactiveOverlayTexture, inactiveOverlayGlowTexture);
+ }
+
+ return TextureFactory.of(texture, getCoverTexture(side));
+ }
+ /*
+ * Fluids
+ */
+
+ /**
+ * The number of fluid (input) slots available for this machine
+ */
+ public int getFluidInputCount() {
+ return 7;
+ }
+
+ /**
+ * The number of fluid (output) slots available for this machine
+ */
+ public int getFluidOutputCount() {
+ return 3;
+ }
+
+ @Override
+ public void setLightValue(byte aLightValue) {}
+
+ /*
+ * Inventory
+ */
+
+ @Override
+ public boolean hasInventoryBeenModified() {
+ // True if the input inventory has changed
+ return hasInventoryChanged;
+ }
+
+ public void markOutputInventoryBeenModified() {
+ outputInventoryChanged = true;
+ }
+
+ public boolean hasOutputInventoryBeenModified() {
+ // True if the output inventory has changed
+ return outputInventoryChanged;
+ }
+
+ public void markInputInventoryBeenModified() {
+ hasInventoryChanged = true;
+ }
+
+ // #region Machine
+
+ @Override
+ public void onPostTick(long tick, boolean isServerSide) {
+ if (isServerSide) {
+ runMachine(tick);
+ } else {
+ doActivitySound(getActivitySoundLoop());
+ }
+ }
+
+ /**
+ * Runs only on server side
+ *
+ * @param tick The current tick of the machine
+ */
+ protected void runMachine(long tick) {
+ if (acceptsFuel() && isActive() && !consumeFuel()) {
+ stopMachine(true);
+ return;
+ }
+
+ if (hasThingsToDo()) {
+ markDirty();
+ runningTick(tick);
+ return;
+ }
+
+ if (tick % TICKS_BETWEEN_RECIPE_CHECKS == 0 || hasWorkJustBeenEnabled()
+ || hasInventoryBeenModified() && isAllowedToWork()) {
+ wasEnabled = false;
+ if (checkRecipe()) {
+ setActive(true);
+ setSound(GregTechTileClientEvents.START_SOUND_LOOP, PROCESS_START_SOUND_INDEX);
+ updateSlots();
+ markDirty();
+ issueClientUpdate();
+ }
+ }
+ }
+
+ /**
+ * Runs only on server side
+ *
+ * @param tick The current tick of the machine
+ */
+ protected void runningTick(long tick) {
+ consumeEnergy();
+ }
+
+ /**
+ * Runs only on server side
+ */
+ protected boolean checkRecipe() {
+ return false;
+ }
+
+ /**
+ * Runs only on server side
+ */
+ protected void consumeEnergy() {
+ PowerLogic logic = getPowerLogic();
+
+ P processing = getProcessingLogic();
+
+ if (!logic.removeEnergyUnsafe(processing.getCalculatedEut())) {
+ stopMachine(true);
+ }
+ }
+
+ public void doSound(byte aIndex, double aX, double aY, double aZ) {
+ switch (aIndex) {
+ case PROCESS_START_SOUND_INDEX -> {
+ if (getProcessStartSound() != null)
+ GT_Utility.doSoundAtClient(getProcessStartSound(), getTimeBetweenProcessSounds(), 1.0F, aX, aY, aZ);
+ }
+ case INTERRUPT_SOUND_INDEX -> GT_Utility
+ .doSoundAtClient(SoundResource.IC2_MACHINES_INTERRUPT_ONE, 100, 1.0F, aX, aY, aZ);
+ }
+ }
+
+ public void startSoundLoop(byte aIndex, double aX, double aY, double aZ) {
+ if (aIndex == PROCESS_START_SOUND_INDEX && getProcessStartSound() != null) {
+ GT_Utility.doSoundAtClient(getProcessStartSound(), getTimeBetweenProcessSounds(), 1.0F, aX, aY, aZ);
+ }
+ }
+
+ protected ResourceLocation getProcessStartSound() {
+ return null;
+ }
+
+ protected int getTimeBetweenProcessSounds() {
+ return 100;
+ }
+
+ @SideOnly(Side.CLIENT)
+ protected void doActivitySound(ResourceLocation activitySound) {
+ if (isActive() && activitySound != null && activitySoundLoop == null) {
+ activitySoundLoop = new GT_SoundLoop(activitySound, this, false, true);
+ Minecraft.getMinecraft()
+ .getSoundHandler()
+ .playSound(activitySoundLoop);
+ return;
+ }
+
+ if (activitySoundLoop != null) {
+ activitySoundLoop = null;
+ }
+
+ }
+
+ @SideOnly(Side.CLIENT)
+ protected ResourceLocation getActivitySoundLoop() {
+ return null;
+ }
+
+ protected ItemStack[] getInputItems() {
+ return itemInput.getStoredItems();
+ }
+
+ protected FluidStack[] getInputFluids() {
+ return fluidInput.getStoredFluids();
+ }
+
+ @Override
+ public int getProgress() {
+ P processing = getProcessingLogic();
+ return processing.getProgress();
+ }
+
+ @Override
+ public int getMaxProgress() {
+ P processing = getProcessingLogic();
+ return processing.getDuration();
+ }
+
+ @Override
+ public boolean increaseProgress(int progressAmount) {
+ P processing = getProcessingLogic();
+ processing.increaseProgress(progressAmount);
+ return true;
+ }
+
+ @Override
+ public boolean hasThingsToDo() {
+ return getMaxProgress() > 0;
+ }
+
+ @Override
+ public boolean hasWorkJustBeenEnabled() {
+ return wasEnabled;
+ }
+
+ @Override
+ public void enableWorking() {
+ wasEnabled = true;
+ canWork = true;
+ }
+
+ @Override
+ public void disableWorking() {
+ canWork = false;
+ }
+
+ @Override
+ public boolean wasShutdown() {
+ return powerShutDown;
+ }
+
+ @Override
+ public boolean isAllowedToWork() {
+ return canWork;
+ }
+
+ @Override
+ public boolean isActive() {
+ return active;
+ }
+
+ @Override
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ protected boolean isElectric() {
+ return isElectric;
+ }
+
+ protected void setElectric(boolean isElectric) {
+ this.isElectric = isElectric;
+ }
+
+ protected boolean isSteam() {
+ return isSteam;
+ }
+
+ protected void setSteam(boolean isSteam) {
+ this.isSteam = isSteam;
+ }
+
+ protected boolean acceptsFuel() {
+ return acceptsFuel;
+ }
+
+ protected void setFuel(boolean acceptsFuel) {
+ this.acceptsFuel = acceptsFuel;
+ }
+
+ protected boolean consumeFuel() {
+ if (isElectric() || isSteam()) return false;
+ if (isActive() && burnTime <= 0) {
+ for (int i = 0; i < itemInput.getSlots(); i++) {
+ ItemStack item = itemInput.getItemInSlot(i);
+ if (item == null) continue;
+ int checkBurnTime = TileEntityFurnace.getItemBurnTime(item) / 10;
+ if (checkBurnTime <= 0) continue;
+ item.stackSize--;
+ burnTime = checkBurnTime;
+ totalBurnTime = checkBurnTime;
+ break;
+ }
+ updateSlots();
+ }
+
+ if (--burnTime < 0) {
+ burnTime = 0;
+ totalBurnTime = 0;
+ return false;
+ }
+ return false;
+ }
+
+ @Override
+ protected void addDebugInfo(EntityPlayer player, int logLevel, ArrayList<String> list) {
+ list.add(
+ GT_Utility.trans("186", "Owned by: ") + EnumChatFormatting.BLUE
+ + getOwnerName()
+ + EnumChatFormatting.RESET
+ + " ("
+ + EnumChatFormatting.AQUA
+ + getOwnerUuid()
+ + EnumChatFormatting.RESET
+ + ")");
+
+ if (acceptsFuel()) {
+ list.add("Fuel: " + EnumChatFormatting.GOLD + burnTime + "/" + totalBurnTime);
+ }
+
+ PowerLogic logic = getPowerLogic();
+ if (isElectric) {
+ list.add(
+ StatCollector.translateToLocal("GT5U.multiblock.energy") + ": "
+ + EnumChatFormatting.GREEN
+ + GT_Utility.formatNumbers(logic.getStoredEnergy())
+ + EnumChatFormatting.RESET
+ + " EU / "
+ + EnumChatFormatting.YELLOW
+ + GT_Utility.formatNumbers(logic.getCapacity())
+ + EnumChatFormatting.RESET
+ + " EU");
+ list.add(
+ StatCollector.translateToLocal("GT5U.multiblock.usage") + ": "
+ + EnumChatFormatting.RED
+ + GT_Utility.formatNumbers(getProcessingLogic().getCalculatedEut())
+ + EnumChatFormatting.RESET
+ + " EU/t");
+ list.add(
+ StatCollector.translateToLocal("GT5U.multiblock.mei") + ": "
+ + EnumChatFormatting.YELLOW
+ + GT_Utility.formatNumbers(logic.getVoltage())
+ + EnumChatFormatting.RESET
+ // TODO: Put ampere getter here, once that's variable
+ + " EU/t(*2A) "
+ + StatCollector.translateToLocal("GT5U.machines.tier")
+ + ": "
+ + EnumChatFormatting.YELLOW
+ + VN[GT_Utility.getTier(logic.getVoltage())]
+ + EnumChatFormatting.RESET);
+ }
+
+ addProgressStringToScanner(player, logLevel, list);
+
+ // TODO: Add CPU load calculator
+ list.add(
+ "Average CPU load of ~" + GT_Utility.formatNumbers(0)
+ + "ns over "
+ + GT_Utility.formatNumbers(0)
+ + " ticks with worst time of "
+ + GT_Utility.formatNumbers(0)
+ + "ns.");
+ }
+
+ protected void addProgressStringToScanner(EntityPlayer player, int logLevel, ArrayList<String> list) {
+ P processing = getProcessingLogic();
+ int progressTime = processing.getProgress();
+ int maxProgressTime = processing.getDuration();
+ list.add(
+ StatCollector.translateToLocal("GT5U.multiblock.Progress") + ": "
+ + EnumChatFormatting.GREEN
+ + GT_Utility.formatNumbers(progressTime > 20 ? progressTime / 20 : progressTime)
+ + EnumChatFormatting.RESET
+ + (progressTime > 20 ? " s / " : " ticks / ")
+ + EnumChatFormatting.YELLOW
+ + GT_Utility.formatNumbers(maxProgressTime > 20 ? maxProgressTime / 20 : maxProgressTime)
+ + EnumChatFormatting.RESET
+ + (maxProgressTime > 20 ? " s" : " ticks"));
+ }
+
+ protected void stopMachine(boolean powerShutDown) {
+ setActive(false);
+ disableWorking();
+ if (powerShutDown) {
+ setSound(GregTechTileClientEvents.STOP_SOUND_LOOP, INTERRUPT_SOUND_INDEX);
+ }
+ issueClientUpdate();
+ }
+
+ protected void updateSlots() {
+ itemInput.update(false);
+ itemOutput.update(false);
+ fluidInput.update();
+ fluidOutput.update();
+ }
+
+ @Override
+ public int getBooleans() {
+ int booleans = 0;
+ if (isActive()) {
+ booleans |= ACTIVE;
+ }
+ return booleans;
+ }
+
+ @Override
+ public void setBooleans(int booleans) {
+ setActive((booleans & ACTIVE) == ACTIVE);
+ }
+
+ public boolean hasItemInput() {
+ return true;
+ }
+
+ public boolean hasItemOutput() {
+ return true;
+ }
+
+ public boolean hasFluidInput() {
+ return true;
+ }
+
+ public boolean hasFluidOutput() {
+ return true;
+ }
+
+ @Override
+ public void setSound(byte soundEvent, int soundEventValue) {
+ this.soundEvent = soundEvent;
+ this.soundEventValue = soundEventValue;
+ if (isServerSide()) {
+ return;
+ }
+
+ switch (soundEventValue) {
+ case PROCESS_START_SOUND_INDEX -> {
+ if (getProcessStartSound() != null) GT_Utility.doSoundAtClient(
+ getProcessStartSound(),
+ getTimeBetweenProcessSounds(),
+ 1.0F,
+ getXCoord(),
+ getYCoord(),
+ getZCoord());
+ }
+ case INTERRUPT_SOUND_INDEX -> GT_Utility.doSoundAtClient(
+ SoundResource.IC2_MACHINES_INTERRUPT_ONE,
+ 100,
+ 1.0F,
+ getXCoord(),
+ getYCoord(),
+ getZCoord());
+ }
+
+ }
+
+ @Nullable
+ public ItemInventoryLogic getItemLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type) {
+ if (side == facing) return null;
+ return switch (type) {
+ case Input -> itemInput;
+ case Output -> itemOutput;
+ default -> null;
+ };
+ }
+
+ @Nullable
+ public FluidInventoryLogic getFluidLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type) {
+ if (side == facing) return null;
+ return switch (type) {
+ case Input -> fluidInput;
+ case Output -> fluidOutput;
+ default -> null;
+ };
+ }
+
+ @Override
+ @Nonnull
+ public P getProcessingLogic() {
+ if (processingLogic == null) {
+ processingLogic = createProcessingLogic().setMachineHost(this);
+ }
+ return Objects.requireNonNull(processingLogic);
+ }
+
+ @OverrideOnly
+ @Nonnull
+ protected abstract P createProcessingLogic();
+
+ @Override
+ public boolean isInputSeparated() {
+ return false;
+ }
+
+ @Nonnull
+ @Override
+ public VoidingMode getVoidMode() {
+ return voidingMode;
+ }
+
+ @Override
+ public boolean needsUpdate() {
+ return processingUpdate;
+ }
+
+ @Override
+ public void setProcessingUpdate(boolean update) {
+ processingUpdate = update;
+ }
+
+ @Override
+ @Nonnull
+ public PowerLogic getPowerLogic(@Nonnull ForgeDirection side) {
+ if (side == facing) return new NullPowerLogic();
+ return power;
+ }
+
+ @Override
+ @Nonnull
+ public ForgeDirection getPowerOutputSide() {
+ return Objects.requireNonNull(facing.getOpposite());
+ }
+
+ protected void updatePowerLogic() {
+ power.setEnergyCapacity(GT_Values.V[tier] * power.getMaxAmperage() * 2 * MINUTE);
+ power.setMaxVoltage(GT_Values.V[tier]);
+ power.setMaxAmperage(1);
+ }
+
+ @Nonnull
+ protected PowerLogic createPowerLogic() {
+ return new PowerLogic().setMaxAmperage(1)
+ .setType(PowerLogic.RECEIVER);
+ }
+
+ @Nonnull
+ protected GUIProvider<?> createGUIProvider() {
+ return new MachineGUIProvider<>(this);
+ }
+
+ @Nonnull
+ public GUIProvider<?> getGUI(@Nonnull UIBuildContext uiContext) {
+ return guiProvider;
+ }
+
+ @Override
+ public ItemStack getAsItem() {
+ return MultiTileEntityRegistry.getRegistry(getMultiTileEntityRegistryID())
+ .getItem(getMultiTileEntityID());
+ }
+
+ @Override
+ public String getMachineName() {
+ return StatCollector.translateToLocal(getAsItem().getUnlocalizedName());
+ }
+
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java
new file mode 100644
index 0000000000..cdcb77d6e5
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java
@@ -0,0 +1,111 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+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.tileentity.TileEntity;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+
+import gregtech.api.logic.ComplexParallelProcessingLogic;
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.GT_Waila;
+import mcp.mobius.waila.api.IWailaConfigHandler;
+import mcp.mobius.waila.api.IWailaDataAccessor;
+
+public abstract class ComplexParallelController<C extends ComplexParallelController<C, P>, P extends ComplexParallelProcessingLogic<P>>
+ extends Controller<C, P> {
+
+ protected int maxComplexParallels = 0;
+ protected int currentComplexParallels = 0;
+
+ public ComplexParallelController() {
+ isSimpleMachine = false;
+ }
+
+ protected void setMaxComplexParallels(int parallel, boolean stopMachine) {
+ if (parallel != maxComplexParallels && maxComplexParallels != 0 && stopMachine) {
+ stopMachine(false);
+ }
+ maxComplexParallels = parallel;
+ setProcessingUpdate(true);
+ }
+
+ @Override
+ protected void stopMachine(boolean powerShutDown) {
+ super.stopMachine(powerShutDown);
+ }
+
+ protected boolean hasPerfectOverclock() {
+ return false;
+ }
+
+ @Override
+ protected void addProgressStringToScanner(EntityPlayer player, int logLevel, ArrayList<String> list) {
+ P processing = getProcessingLogic();
+ for (int i = 0; i < maxComplexParallels; i++) {
+ list.add(
+ StatCollector.translateToLocal("GT5U.multiblock.Progress") + " "
+ + (i + 1)
+ + ": "
+ + EnumChatFormatting.GREEN
+ + GT_Utility.formatNumbers(
+ processing.getProgress(i) > 20 ? processing.getProgress(i) / 20 : processing.getProgress(i))
+ + EnumChatFormatting.RESET
+ + (processing.getProgress(i) > 20 ? " s / " : " ticks / ")
+ + EnumChatFormatting.YELLOW
+ + GT_Utility.formatNumbers(
+ processing.getDuration(i) > 20 ? processing.getDuration(i) / 20 : processing.getDuration(i))
+ + EnumChatFormatting.RESET
+ + (processing.getDuration(i) > 20 ? " s" : " ticks"));
+ }
+ }
+
+ @Override
+ public void getWailaNBTData(EntityPlayerMP player, TileEntity tile, NBTTagCompound tag, World world, int x, int y,
+ int z) {
+ super.getWailaNBTData(player, tile, tag, world, x, y, z);
+ P processing = getProcessingLogic();
+ tag.setInteger("maxComplexParallels", maxComplexParallels);
+ for (int i = 0; i < maxComplexParallels; i++) {
+ tag.setInteger("maxProgress" + i, processing.getDuration(i));
+ tag.setInteger("progress" + i, processing.getProgress(i));
+ }
+ }
+
+ @Override
+ public void getWailaBody(ItemStack itemStack, List<String> currentTip, IWailaDataAccessor accessor,
+ IWailaConfigHandler config) {
+ super.getWailaBody(itemStack, currentTip, accessor, config);
+ final NBTTagCompound tag = accessor.getNBTData();
+ maxComplexParallels = tag.getInteger("maxComplexParallels");
+ for (int i = 0; i < maxComplexParallels; i++) {
+ long maxProgress = tag.getInteger("maxProgress" + i);
+ long progress = tag.getInteger("progress" + i);
+ currentTip.add(
+ "Process " + (i + 1)
+ + ": "
+ + GT_Waila
+ .getMachineProgressString(maxProgress > 0 && maxProgress >= progress, maxProgress, progress));
+ }
+ }
+
+ @Override
+ public void setProcessingLogicPower(@Nonnull P processingLogic) {
+ processingLogic.setAmperageOC(true);
+ processingLogic.setAvailableAmperage(getPowerLogic().getMaxAmperage() / maxComplexParallels);
+ processingLogic.setAvailableVoltage(getPowerLogic().getVoltage() / maxComplexParallels);
+ }
+
+ @Override
+ public void updateProcessingLogic(@Nonnull P processingLogic) {
+ processingLogic.setMaxComplexParallel(maxComplexParallels);
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java
new file mode 100644
index 0000000000..7ffdc4fb60
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java
@@ -0,0 +1,1083 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import static gregtech.api.util.GT_Utility.moveMultipleItemStacks;
+import static gregtech.common.misc.WirelessNetworkManager.strongCheckOrAddUser;
+import static mcp.mobius.waila.api.SpecialChars.*;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.lwjgl.input.Keyboard;
+
+import com.gtnewhorizon.structurelib.StructureLibAPI;
+import com.gtnewhorizon.structurelib.alignment.IAlignment;
+import com.gtnewhorizon.structurelib.alignment.IAlignmentLimits;
+import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable;
+import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
+import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
+import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
+import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
+import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
+import com.gtnewhorizon.structurelib.util.Vec3Impl;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+
+import cpw.mods.fml.common.network.NetworkRegistry;
+import gregtech.api.enums.GT_Values.NBT;
+import gregtech.api.enums.InventoryType;
+import gregtech.api.enums.VoidingMode;
+import gregtech.api.interfaces.IDescribable;
+import gregtech.api.interfaces.fluid.IFluidStore;
+import gregtech.api.logic.ControllerFluidLogic;
+import gregtech.api.logic.ControllerItemLogic;
+import gregtech.api.logic.FluidInventoryLogic;
+import gregtech.api.logic.ItemInventoryLogic;
+import gregtech.api.logic.MuTEProcessingLogic;
+import gregtech.api.logic.PowerLogic;
+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_AddToolTips;
+import gregtech.api.multitileentity.machine.MultiTileBasicMachine;
+import gregtech.api.multitileentity.multiblock.casing.FunctionalCasing;
+import gregtech.api.multitileentity.multiblock.casing.UpgradeCasing;
+import gregtech.api.net.GT_Packet_MultiTileEntity;
+import gregtech.api.objects.GT_ItemStack;
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.GT_Waila;
+import mcp.mobius.waila.api.IWailaConfigHandler;
+import mcp.mobius.waila.api.IWailaDataAccessor;
+
+/**
+ * Multi Tile Entities - or MuTEs - don't have dedicated hatches, but their casings can become hatches.
+ */
+public abstract class Controller<C extends Controller<C, P>, P extends MuTEProcessingLogic<P>>
+ extends MultiTileBasicMachine<P>
+ implements IAlignment, IMultiBlockController, IDescribable, IMTE_AddToolTips, ISurvivalConstructable {
+
+ public static final String ALL_INVENTORIES_NAME = "all";
+ protected static final int AUTO_OUTPUT_FREQUENCY_TICK = 20;
+
+ private static final Map<Integer, GT_Multiblock_Tooltip_Builder> tooltip = new ConcurrentHashMap<>();
+ private final List<UpgradeCasing> upgradeCasings = new ArrayList<>();
+ private final List<FunctionalCasing> functionalCasings = new ArrayList<>();
+ protected BuildState buildState = new BuildState();
+
+ private boolean structureOkay = false, structureChanged = false;
+ private ExtendedFacing extendedFacing = ExtendedFacing.DEFAULT;
+ private IAlignmentLimits limits = getInitialAlignmentLimits();
+ protected boolean separateInputs = getDefaultInputSeparationMode();
+ protected VoidingMode voidingMode = getDefaultVoidingMode();
+ protected boolean batchMode = getDefaultBatchMode();
+ protected boolean recipeLock = getDefaultRecipeLockingMode();
+ protected boolean shouldSort = false;
+ /** If this is set to true, the machine will get default WAILA behavior */
+ protected boolean isSimpleMachine = true;
+
+ protected boolean isCleanroom = false;
+ protected ControllerItemLogic controllerItemInput = new ControllerItemLogic();
+ protected ControllerItemLogic controllerItemOutput = new ControllerItemLogic();
+ protected ControllerFluidLogic controllerFluidInput = new ControllerFluidLogic();
+ protected ControllerFluidLogic controllerFluidOutput = new ControllerFluidLogic();
+
+ // A list of sides
+ // Each side has a list of parts that have a cover that need to be ticked
+ protected List<LinkedList<WeakReference<IMultiBlockPart>>> registeredCoveredParts = Arrays.asList(
+ new LinkedList<>(),
+ new LinkedList<>(),
+ new LinkedList<>(),
+ new LinkedList<>(),
+ new LinkedList<>(),
+ new LinkedList<>());
+
+ // A list for each purpose that a casing can register to, to be ticked
+ protected List<LinkedList<WeakReference<IMultiBlockPart>>> registeredTickableParts = new ArrayList<>();
+
+ public Controller() {
+ for (int i = 0; i < MultiTileCasingPurpose.values().length; i++) {
+ registeredTickableParts.add(new LinkedList<>());
+ }
+ }
+
+ /** Registry ID of the required casing */
+ public abstract short getCasingRegistryID();
+
+ /** Meta ID of the required casing */
+ public abstract int getCasingMeta();
+
+ /**
+ * Create the tooltip for this multi block controller.
+ */
+ protected abstract GT_Multiblock_Tooltip_Builder createTooltip();
+
+ /**
+ * @return The starting offset for the structure builder
+ */
+ public abstract Vec3Impl getStartingStructureOffset();
+
+ /**
+ * Due to limitation of Java type system, you might need to do an unchecked cast. HOWEVER, the returned
+ * IStructureDefinition is expected to be evaluated against current instance only, and should not be used against
+ * other instances, even for those of the same class.
+ */
+ @Override
+ public abstract IStructureDefinition<C> getStructureDefinition();
+
+ /**
+ * Checks the Machine.
+ * <p>
+ * NOTE: If using `buildState` be sure to `startBuilding()` and either `endBuilding()` or `failBuilding()`
+ */
+ public boolean checkMachine() {
+ calculateTier();
+ updatePowerLogic();
+ return tier > 0;
+ }
+
+ protected void calculateTier() {
+ double sum = 0;
+ if (functionalCasings == null || functionalCasings.size() == 0) {
+ return;
+ }
+ for (FunctionalCasing casing : functionalCasings) {
+ sum += casing.getPartTier() * casing.getPartModifier();
+ }
+ tier = (int) Math.min(Math.floor(sum / functionalCasings.size()), 14);
+ }
+
+ @Override
+ public void writeMultiTileNBT(NBTTagCompound nbt) {
+ super.writeMultiTileNBT(nbt);
+
+ nbt.setBoolean(NBT.STRUCTURE_OK, structureOkay);
+ nbt.setByte(
+ NBT.ROTATION,
+ (byte) extendedFacing.getRotation()
+ .getIndex());
+ nbt.setByte(
+ NBT.FLIP,
+ (byte) extendedFacing.getFlip()
+ .getIndex());
+
+ nbt.setString(NBT.VOIDING_MODE, voidingMode.name);
+ nbt.setBoolean(NBT.SEPARATE_INPUTS, separateInputs);
+ nbt.setBoolean(NBT.RECIPE_LOCK, recipeLock);
+ nbt.setBoolean(NBT.BATCH_MODE, batchMode);
+ }
+
+ @Override
+ protected void saveItemLogic(NBTTagCompound nbt) {
+ NBTTagCompound itemInputNBT = controllerItemInput.saveToNBT();
+ nbt.setTag(NBT.INV_INPUT_LIST, itemInputNBT);
+ NBTTagCompound itemOutputNBT = controllerItemOutput.saveToNBT();
+ nbt.setTag(NBT.INV_OUTPUT_LIST, itemOutputNBT);
+ }
+
+ @Override
+ protected void saveFluidLogic(NBTTagCompound nbt) {
+ NBTTagCompound fluidInputNBT = controllerFluidInput.saveToNBT();
+ nbt.setTag(NBT.TANK_IN, fluidInputNBT);
+ NBTTagCompound fluidOutputNBT = controllerFluidOutput.saveToNBT();
+ nbt.setTag(NBT.TANK_OUT, fluidOutputNBT);
+ }
+
+ @Override
+ public void readMultiTileNBT(NBTTagCompound nbt) {
+ super.readMultiTileNBT(nbt);
+
+ // Multiblock inventories are a collection of inventories. The first inventory is the default internal
+ // inventory, and the others are added by inventory extending blocks.
+
+ structureOkay = nbt.getBoolean(NBT.STRUCTURE_OK);
+ extendedFacing = ExtendedFacing
+ .of(getFrontFacing(), Rotation.byIndex(nbt.getByte(NBT.ROTATION)), Flip.byIndex(nbt.getByte(NBT.FLIP)));
+
+ voidingMode = VoidingMode.fromName(nbt.getString(NBT.VOIDING_MODE));
+ separateInputs = nbt.getBoolean(NBT.SEPARATE_INPUTS);
+ recipeLock = nbt.getBoolean(NBT.RECIPE_LOCK);
+ batchMode = nbt.getBoolean(NBT.BATCH_MODE);
+ }
+
+ @Override
+ protected void loadItemLogic(NBTTagCompound nbt) {
+ if (!nbt.hasKey(NBT.INV_INPUT_LIST) && !nbt.hasKey(NBT.INV_OUTPUT_LIST)) {
+ controllerItemInput.addInventory(new ItemInventoryLogic(16));
+ controllerItemOutput.addInventory(new ItemInventoryLogic(16));
+ return;
+ }
+ controllerItemInput.loadFromNBT(nbt.getCompoundTag(NBT.INV_INPUT_LIST));
+ controllerItemOutput.loadFromNBT(nbt.getCompoundTag(NBT.INV_OUTPUT_LIST));
+ }
+
+ @Override
+ protected void loadFluidLogic(NBTTagCompound nbt) {
+ if (!nbt.hasKey(NBT.TANK_IN) && !nbt.hasKey(NBT.TANK_OUT)) {
+ controllerFluidInput.addInventory(new FluidInventoryLogic(16, 32000));
+ controllerFluidOutput.addInventory(new FluidInventoryLogic(16, 32000));
+ return;
+ }
+ controllerFluidInput.loadFromNBT(nbt.getCompoundTag(NBT.TANK_IN));
+ controllerFluidOutput.loadFromNBT(nbt.getCompoundTag(NBT.TANK_OUT));
+ }
+
+ @Override
+ public void addToolTips(List<String> aList, ItemStack aStack, boolean aF3_H) {
+ aList.addAll(Arrays.asList(getDescription()));
+ }
+
+ @Override
+ public String[] getDescription() {
+ if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
+ return getTooltip().getStructureInformation();
+ }
+
+ return getTooltip().getInformation();
+ }
+
+ @Override
+ protected void addDebugInfo(EntityPlayer aPlayer, int aLogLevel, ArrayList<String> tList) {
+ super.addDebugInfo(aPlayer, aLogLevel, tList);
+ tList.add("Structure ok: " + checkStructure(false));
+ }
+
+ protected int getToolTipID() {
+ return getMultiTileEntityRegistryID() << 16 + getMultiTileEntityID();
+ }
+
+ protected GT_Multiblock_Tooltip_Builder getTooltip() {
+ GT_Multiblock_Tooltip_Builder builder = tooltip.get(getToolTipID());
+ if (builder == null) {
+ builder = createTooltip();
+ tooltip.put(getToolTipID(), builder);
+ }
+ return builder;
+ }
+
+ @Override
+ public boolean checkStructure(boolean aForceReset) {
+ if (!isServerSide()) return structureOkay;
+
+ // Only trigger an update if forced (from onPostTick, generally), or if the structure has changed
+ if ((structureChanged || aForceReset)) {
+ clearSpecialLists();
+ structureOkay = checkMachine();
+ }
+ structureChanged = false;
+ return structureOkay;
+ }
+
+ @Override
+ public void onStructureChange() {
+ structureChanged = true;
+ }
+
+ public final boolean checkPiece(String piece, Vec3Impl offset) {
+ return checkPiece(piece, offset.get0(), offset.get1(), offset.get2());
+ }
+
+ /**
+ * Explanation of the world coordinate these offset means:
+ * <p>
+ * Imagine you stand in front of the controller, with controller facing towards you not rotated or flipped.
+ * <p>
+ * The horizontalOffset would be the number of blocks on the left side of the controller, not counting controller
+ * itself. The verticalOffset would be the number of blocks on the top side of the controller, not counting
+ * controller itself. The depthOffset would be the number of blocks between you and controller, not counting
+ * controller itself.
+ * <p>
+ * All these offsets can be negative.
+ */
+ protected final boolean checkPiece(String piece, int horizontalOffset, int verticalOffset, int depthOffset) {
+ return getCastedStructureDefinition().check(
+ this,
+ piece,
+ getWorld(),
+ getExtendedFacing(),
+ getXCoord(),
+ getYCoord(),
+ getZCoord(),
+ horizontalOffset,
+ verticalOffset,
+ depthOffset,
+ !structureOkay);
+ }
+
+ public final boolean buildPiece(String piece, ItemStack trigger, boolean hintsOnly, Vec3Impl offset) {
+ return buildPiece(piece, trigger, hintsOnly, offset.get0(), offset.get1(), offset.get2());
+ }
+
+ protected final boolean buildPiece(String piece, ItemStack trigger, boolean hintOnly, int horizontalOffset,
+ int verticalOffset, int depthOffset) {
+ return getCastedStructureDefinition().buildOrHints(
+ this,
+ trigger,
+ piece,
+ getWorld(),
+ getExtendedFacing(),
+ getXCoord(),
+ getYCoord(),
+ getZCoord(),
+ horizontalOffset,
+ verticalOffset,
+ depthOffset,
+ hintOnly);
+ }
+
+ protected final int survivalBuildPiece(String piece, ItemStack trigger, Vec3Impl offset, int elementBudget,
+ ISurvivalBuildEnvironment env, boolean check) {
+ return survivalBuildPiece(
+ piece,
+ trigger,
+ offset.get0(),
+ offset.get1(),
+ offset.get2(),
+ elementBudget,
+ env,
+ check);
+ }
+
+ protected final Integer survivalBuildPiece(String piece, ItemStack trigger, int horizontalOffset,
+ int verticalOffset, int depthOffset, int elementBudget, ISurvivalBuildEnvironment env, boolean check) {
+ return getCastedStructureDefinition().survivalBuild(
+ this,
+ trigger,
+ piece,
+ getWorld(),
+ getExtendedFacing(),
+ getXCoord(),
+ getYCoord(),
+ getZCoord(),
+ horizontalOffset,
+ verticalOffset,
+ depthOffset,
+ elementBudget,
+ env,
+ check);
+ }
+
+ @SuppressWarnings("unchecked")
+ private IStructureDefinition<Controller<C, P>> getCastedStructureDefinition() {
+ return (IStructureDefinition<Controller<C, P>>) getStructureDefinition();
+ }
+
+ @Override
+ public ExtendedFacing getExtendedFacing() {
+ return extendedFacing;
+ }
+
+ @Override
+ public void setExtendedFacing(ExtendedFacing newExtendedFacing) {
+ if (extendedFacing == newExtendedFacing) {
+ return;
+ }
+
+ onStructureChange();
+ if (structureOkay) stopMachine(false);
+ extendedFacing = newExtendedFacing;
+ structureOkay = false;
+ if (isServerSide()) {
+ StructureLibAPI.sendAlignment(
+ this,
+ new NetworkRegistry.TargetPoint(
+ getWorld().provider.dimensionId,
+ getXCoord(),
+ getYCoord(),
+ getZCoord(),
+ 512));
+ } else {
+ issueTextureUpdate();
+ }
+
+ }
+
+ @Override
+ public boolean onWrenchRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
+ float aY, float aZ, ItemStack aTool) {
+ if (wrenchSide != getFrontFacing())
+ return super.onWrenchRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ);
+ if (aPlayer.isSneaking()) {
+ // we won't be allowing horizontal flips, as it can be perfectly emulated by rotating twice and flipping
+ // horizontally allowing an extra round of flip make it hard to draw meaningful flip markers in
+ // GT_Proxy#drawGrid
+ toolSetFlip(getFlip().isHorizontallyFlipped() ? Flip.NONE : Flip.HORIZONTAL);
+ } else {
+ toolSetRotation(null);
+ }
+ return true;
+ }
+
+ @Override
+ public void registerCoveredPartOnSide(final ForgeDirection side, IMultiBlockPart part) {
+ if (side == ForgeDirection.UNKNOWN) return;
+
+ final LinkedList<WeakReference<IMultiBlockPart>> registeredCovers = registeredCoveredParts.get(side.ordinal());
+ // TODO: Make sure that we're not already registered on this side
+ registeredCovers.add(new WeakReference<>(part));
+ }
+
+ @Override
+ public void unregisterCoveredPartOnSide(final ForgeDirection side, IMultiBlockPart aPart) {
+ if (side == ForgeDirection.UNKNOWN) return;
+
+ final LinkedList<WeakReference<IMultiBlockPart>> coveredParts = registeredCoveredParts.get(side.ordinal());
+ final Iterator<WeakReference<IMultiBlockPart>> it = coveredParts.iterator();
+ while (it.hasNext()) {
+ final IMultiBlockPart part = (it.next()).get();
+ if (part == null || part == aPart) it.remove();
+ }
+ }
+
+ @Override
+ public void registerCaseWithPurpose(MultiTileCasingPurpose purpose, IMultiBlockPart part) {
+ final LinkedList<WeakReference<IMultiBlockPart>> tickableParts = registeredTickableParts.get(purpose.ordinal());
+ final Iterator<WeakReference<IMultiBlockPart>> it = tickableParts.iterator();
+ while (it.hasNext()) {
+ final IMultiBlockPart next = (it.next()).get();
+ if (next == null) {
+ it.remove();
+ } else if (next == part) {
+ return;
+ }
+ }
+ tickableParts.add(new WeakReference<>(part));
+ }
+
+ @Override
+ public void unregisterCaseWithPurpose(MultiTileCasingPurpose purpose, IMultiBlockPart part) {
+ final LinkedList<WeakReference<IMultiBlockPart>> tickableParts = registeredTickableParts.get(purpose.ordinal());
+ final Iterator<WeakReference<IMultiBlockPart>> it = tickableParts.iterator();
+ while (it.hasNext()) {
+ final IMultiBlockPart next = (it.next()).get();
+ if (next == null || next == part) it.remove();
+ }
+ }
+
+ @Override
+ public void onFirstTick(boolean isServerSide) {
+ super.onFirstTick(isServerSide);
+ if (isServerSide) {
+ checkStructure(true);
+ } else {
+ StructureLibAPI.queryAlignment(this);
+ }
+ }
+
+ private boolean tickCovers() {
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ // TODO: Tick controller covers, if any
+ final LinkedList<WeakReference<IMultiBlockPart>> coveredParts = this.registeredCoveredParts
+ .get(side.ordinal());
+ final Iterator<WeakReference<IMultiBlockPart>> it = coveredParts.iterator();
+ while (it.hasNext()) {
+ final IMultiBlockPart part = (it.next()).get();
+ if (part == null) {
+ it.remove();
+ continue;
+ }
+ if (!part.tickCoverAtSide(side, mTickTimer)) it.remove();
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onTick(long tick, boolean isServerSide) {
+ if (!tickCovers()) {
+ return;
+ }
+ }
+
+ @Override
+ public void onPostTick(long tick, boolean isServerSide) {
+ if (!isServerSide) { // client side
+ doActivitySound(getActivitySoundLoop());
+ return;
+ }
+
+ // server side
+ if (tick % 600 == 5) {
+ // Recheck the structure every 30 seconds or so
+ if (!checkStructure(false)) checkStructure(true);
+ }
+ if (checkStructure(false)) {
+ runMachine(tick);
+ pushItemOutputs(tick);
+ pushFluidOutputs(tick);
+
+ } else {
+ stopMachine(false);
+ }
+
+ }
+
+ protected void pushItemOutputs(long tick) {
+ if (tick % AUTO_OUTPUT_FREQUENCY_TICK != 0) return;
+ final LinkedList<WeakReference<IMultiBlockPart>> registeredItemOutputs = registeredTickableParts
+ .get(MultiTileCasingPurpose.ItemOutput.ordinal());
+ final Iterator<WeakReference<IMultiBlockPart>> itemOutputIterator = registeredItemOutputs.iterator();
+ while (itemOutputIterator.hasNext()) {
+ final IMultiBlockPart part = (itemOutputIterator.next()).get();
+ if (part == null) {
+ itemOutputIterator.remove();
+ continue;
+ }
+ if (!part.shouldTick(mTickTimer)) {
+ itemOutputIterator.remove();
+ continue;
+ }
+
+ final IInventory facingInventory = part.getIInventoryAtSide(part.getFrontFacing());
+ if (facingInventory == null) {
+ continue;
+ }
+
+ moveMultipleItemStacks(
+ part,
+ facingInventory,
+ part.getFrontFacing(),
+ part.getBackFacing(),
+ null,
+ false,
+ (byte) 64,
+ (byte) 1,
+ (byte) 64,
+ (byte) 1,
+ part.getSizeInventory());
+ for (int i = 0; i < part.getSizeInventory(); i++) {
+ if (part.getStackInSlot(i) != null && part.getStackInSlot(i).stackSize <= 0) {
+ part.setInventorySlotContents(i, null);
+ }
+ }
+
+ }
+ }
+
+ protected void pushFluidOutputs(long tick) {
+ if (tick % AUTO_OUTPUT_FREQUENCY_TICK != 0) return;
+ final LinkedList<WeakReference<IMultiBlockPart>> registeredFluidOutputs = registeredTickableParts
+ .get(MultiTileCasingPurpose.FluidOutput.ordinal());
+ final Iterator<WeakReference<IMultiBlockPart>> fluidOutputIterator = registeredFluidOutputs.iterator();
+ while (fluidOutputIterator.hasNext()) {
+ final IMultiBlockPart part = (fluidOutputIterator.next()).get();
+ if (part == null) {
+ fluidOutputIterator.remove();
+ continue;
+ }
+ if (!part.shouldTick(mTickTimer)) {
+ fluidOutputIterator.remove();
+ }
+ }
+ }
+
+ @Override
+ public void setCleanroom(boolean cleanroom) {
+ isCleanroom = cleanroom;
+ }
+
+ protected void clearSpecialLists() {
+ upgradeCasings.clear();
+ functionalCasings.clear();
+ }
+
+ @Override
+ public final boolean isFacingValid(ForgeDirection facing) {
+ return canSetToDirectionAny(facing);
+ }
+
+ @Override
+ public void onFacingChange() {
+ toolSetDirection(getFrontFacing());
+ onStructureChange();
+ }
+
+ @Override
+ public boolean allowCoverOnSide(ForgeDirection side, GT_ItemStack aCoverID) {
+ return side != facing;
+ }
+
+ @Override
+ public String[] getStructureDescription(ItemStack stackSize) {
+ return getTooltip().getStructureHint();
+ }
+
+ @Override
+ public IAlignmentLimits getAlignmentLimits() {
+ return limits;
+ }
+
+ protected void setAlignmentLimits(IAlignmentLimits mLimits) {
+ this.limits = mLimits;
+ }
+
+ public boolean isSeparateInputs() {
+ return separateInputs;
+ }
+
+ public void setSeparateInputs(boolean aSeparateInputs) {
+ separateInputs = aSeparateInputs;
+ }
+
+ protected IAlignmentLimits getInitialAlignmentLimits() {
+ return (d, r, f) -> !f.isVerticallyFliped();
+ }
+
+ public static class BuildState {
+
+ /**
+ * Utility class to keep track of the build state of a multiblock
+ */
+ boolean building = false;
+
+ Vec3Impl currentOffset;
+
+ public void startBuilding(Vec3Impl structureOffset) {
+ if (building) throw new IllegalStateException("Already building!");
+ building = true;
+ setCurrentOffset(structureOffset);
+ }
+
+ public Vec3Impl setCurrentOffset(Vec3Impl structureOffset) {
+ verifyBuilding();
+ return (currentOffset = structureOffset);
+ }
+
+ private void verifyBuilding() {
+ if (!building) throw new IllegalStateException("Not building!");
+ }
+
+ public boolean failBuilding() {
+ building = false;
+ currentOffset = null;
+ return false;
+ }
+
+ public Vec3Impl stopBuilding() {
+ final Vec3Impl toReturn = getCurrentOffset();
+ building = false;
+ currentOffset = null;
+
+ return toReturn;
+ }
+
+ public Vec3Impl getCurrentOffset() {
+ verifyBuilding();
+ return currentOffset;
+ }
+
+ public Vec3Impl addOffset(Vec3Impl offset) {
+ verifyBuilding();
+ return setCurrentOffset(currentOffset.add(offset));
+ }
+ }
+
+ public void registerSpecialCasings(MultiBlockPart part) {
+ if (part instanceof UpgradeCasing) {
+ upgradeCasings.add((UpgradeCasing) part);
+ }
+ if (part instanceof FunctionalCasing) {
+ functionalCasings.add((FunctionalCasing) part);
+ }
+ }
+
+ // #region Fluid - MultiBlock related Fluid Tank behaviour.
+
+ @Override
+ @Nullable
+ public FluidInventoryLogic getFluidLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type) {
+ if (side == facing) return null;
+ return switch (type) {
+ case Input -> controllerFluidInput.getAllInventoryLogics();
+ case Output -> controllerFluidOutput.getAllInventoryLogics();
+ default -> null;
+ };
+ }
+
+ @Nullable
+ public FluidInventoryLogic getFluidLogic(@Nonnull InventoryType type, @Nullable UUID id) {
+ return switch (type) {
+ case Input -> controllerFluidInput.getInventoryLogic(id);
+ case Output -> controllerFluidOutput.getInventoryLogic(id);
+ default -> null;
+ };
+ }
+
+ @Override
+ @Nonnull
+ public UUID registerFluidInventory(int tanks, long capacity, int tier, @Nonnull InventoryType type,
+ boolean isUpgradeInventory) {
+ return switch (type) {
+ case Input -> controllerFluidInput
+ .addInventory(new FluidInventoryLogic(tanks, capacity, tier, isUpgradeInventory));
+ case Output -> controllerFluidOutput
+ .addInventory(new FluidInventoryLogic(tanks, capacity, tier, isUpgradeInventory));
+ case Both -> {
+ UUID id = controllerFluidInput
+ .addInventory(new FluidInventoryLogic(tanks, capacity, tier, isUpgradeInventory));
+ controllerFluidOutput
+ .addInventory(id, new FluidInventoryLogic(tanks, capacity, tier, isUpgradeInventory));
+ yield id;
+ }
+ };
+ }
+
+ @Override
+ @Nonnull
+ public FluidInventoryLogic unregisterFluidInventory(@Nonnull UUID id, @Nonnull InventoryType type) {
+ return switch (type) {
+ case Input -> controllerFluidInput.removeInventory(id);
+ case Output -> controllerFluidOutput.removeInventory(id);
+ case Both -> {
+ FluidInventoryLogic input = controllerFluidInput.removeInventory(id);
+ FluidInventoryLogic output = controllerFluidOutput.removeInventory(id);
+ yield new FluidInventoryLogic(
+ Arrays.asList(input, output)
+ .stream()
+ .map(inv -> inv.getInventory())
+ .collect(Collectors.toList()));
+ }
+ };
+ }
+
+ @Override
+ public void changeFluidInventoryDisplayName(@Nullable UUID id, @Nullable String displayName,
+ @Nonnull InventoryType type) {
+ switch (type) {
+ case Input:
+ controllerFluidInput.setInventoryDisplayName(id, displayName);
+ break;
+ case Output:
+ controllerFluidOutput.setInventoryDisplayName(id, displayName);
+ break;
+ case Both:
+ controllerFluidInput.setInventoryDisplayName(id, displayName);
+ controllerFluidOutput.setInventoryDisplayName(id, displayName);
+ break;
+ }
+ }
+
+ // #endregion Fluid
+
+ // #region Item - MultiBlock related Item behaviour.
+
+ @Override
+ @Nullable
+ public ItemInventoryLogic getItemLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type) {
+ if (side == facing) return null;
+ return switch (type) {
+ case Input -> controllerItemInput.getAllInventoryLogics();
+ case Output -> controllerItemOutput.getAllInventoryLogics();
+ default -> null;
+ };
+ }
+
+ @Override
+ @Nullable
+ public ItemInventoryLogic getItemLogic(@Nonnull InventoryType type, @Nullable UUID id) {
+ return switch (type) {
+ case Input -> controllerItemInput.getInventoryLogic(id);
+ case Output -> controllerItemOutput.getInventoryLogic(id);
+ default -> null;
+ };
+ }
+
+ @Override
+ @Nonnull
+ public UUID registerItemInventory(int slots, int tier, @Nonnull InventoryType type, boolean isUpgradeInventory) {
+ return switch (type) {
+ case Input -> controllerItemInput.addInventory(new ItemInventoryLogic(slots, tier, isUpgradeInventory));
+ case Output -> controllerItemOutput.addInventory(new ItemInventoryLogic(slots, tier, isUpgradeInventory));
+ case Both -> {
+ UUID id = controllerItemInput.addInventory(new ItemInventoryLogic(slots, tier, isUpgradeInventory));
+ controllerItemOutput.addInventory(id, new ItemInventoryLogic(slots, tier, isUpgradeInventory));
+ yield id;
+ }
+ };
+ }
+
+ @Override
+ public ItemInventoryLogic unregisterItemInventory(@Nonnull UUID id, @Nonnull InventoryType type) {
+ return switch (type) {
+ case Input -> controllerItemInput.removeInventory(id);
+ case Output -> controllerItemOutput.removeInventory(id);
+ case Both -> {
+ ItemInventoryLogic input = controllerItemInput.removeInventory(id);
+ ItemInventoryLogic output = controllerItemOutput.removeInventory(id);
+ yield new ItemInventoryLogic(
+ Arrays.asList(input, output)
+ .stream()
+ .map(inv -> inv.getInventory())
+ .collect(Collectors.toList()));
+ }
+ };
+ }
+
+ @Override
+ public void changeItemInventoryDisplayName(@Nullable UUID id, @Nullable String displayName,
+ @Nonnull InventoryType type) {
+ switch (type) {
+ case Input:
+ controllerItemInput.setInventoryDisplayName(id, displayName);
+ break;
+ case Output:
+ controllerItemOutput.setInventoryDisplayName(id, displayName);
+ break;
+ case Both:
+ controllerItemInput.setInventoryDisplayName(id, displayName);
+ controllerItemOutput.setInventoryDisplayName(id, displayName);
+ break;
+ }
+ }
+
+ // #endregion Item
+
+ // #region Energy
+
+ @Nonnull
+ @Override
+ public PowerLogic getPowerLogic() {
+ return getPowerLogic(ForgeDirection.UNKNOWN);
+ }
+
+ // #endregion Energy
+
+ @Override
+ protected void updateSlots() {
+ controllerItemInput.getAllInventoryLogics()
+ .update(shouldSort);
+ controllerItemOutput.getAllInventoryLogics()
+ .update(shouldSort);
+ controllerFluidInput.getAllInventoryLogics()
+ .update();
+ controllerFluidOutput.getAllInventoryLogics()
+ .update();
+ }
+
+ /*
+ * GUI Work - Multiblock GUI related methods
+ */
+ @Override
+ public boolean useModularUI() {
+ return true;
+ }
+
+ @Override
+ public boolean hasGui(ForgeDirection side) {
+ return true;
+ }
+
+ @Override
+ protected void addTitleTextStyle(ModularWindow.Builder builder, String title) {
+ // leave empty
+ }
+
+ @Override
+ public boolean supportsVoidProtection() {
+ return true;
+ }
+
+ @Override
+ public VoidingMode getVoidingMode() {
+ return voidingMode;
+ }
+
+ @Override
+ public void setVoidingMode(VoidingMode mode) {
+ this.voidingMode = mode;
+ }
+
+ @Override
+ public boolean canDumpItemToME() {
+ return false;
+ }
+
+ @Override
+ public boolean canDumpFluidToME() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsInputSeparation() {
+ return true;
+ }
+
+ @Override
+ public boolean isInputSeparated() {
+ return separateInputs;
+ }
+
+ @Override
+ public void setInputSeparation(Boolean enabled) {
+ this.separateInputs = enabled;
+ }
+
+ @Override
+ public boolean supportsBatchMode() {
+ return true;
+ }
+
+ @Override
+ public boolean isBatchModeEnabled() {
+ return batchMode;
+ }
+
+ @Override
+ public void setBatchMode(Boolean mode) {
+ this.batchMode = mode;
+ }
+
+ @Override
+ public boolean supportsSingleRecipeLocking() {
+ return false;
+ }
+
+ @Override
+ public boolean isRecipeLockingEnabled() {
+ return recipeLock;
+ }
+
+ @Override
+ public void setRecipeLocking(Boolean enabled) {
+ this.recipeLock = enabled;
+ }
+
+ @Override
+ public void getWailaNBTData(EntityPlayerMP player, TileEntity tile, NBTTagCompound tag, World world, int x, int y,
+ int z) {
+ super.getWailaNBTData(player, tile, tag, world, x, y, z);
+ P processing = getProcessingLogic();
+ tag.setInteger("progress", processing.getProgress());
+ tag.setInteger("maxProgress", processing.getDuration());
+ tag.setBoolean("structureOkay", structureOkay);
+ tag.setBoolean("isActive", isActive());
+ if (isActive()) {
+ tag.setLong("energyUsage", getProcessingLogic().getCalculatedEut());
+ tag.setLong("energyTier", tier);
+ }
+ }
+
+ @Override
+ public void getWailaBody(ItemStack itemStack, List<String> currentTip, IWailaDataAccessor accessor,
+ IWailaConfigHandler config) {
+ super.getWailaBody(itemStack, currentTip, accessor, config);
+ final NBTTagCompound tag = accessor.getNBTData();
+ if (!tag.getBoolean("structureOkay")) {
+ currentTip.add(RED + "** INCOMPLETE STRUCTURE **" + RESET);
+ } else {
+ currentTip.add((GREEN + "Running Fine") + RESET);
+ }
+ if (isSimpleMachine) {
+ boolean isActive = tag.getBoolean("isActive");
+ currentTip.add(
+ GT_Waila.getMachineProgressString(isActive, tag.getInteger("maxProgress"), tag.getInteger("progress")));
+ }
+ boolean isActive = tag.getBoolean("isActive");
+ if (isActive) {
+ long energyTier = tag.getLong("energyTier");
+ long actualEnergyUsage = tag.getLong("energyUsage");
+ if (actualEnergyUsage > 0) {
+ currentTip.add(
+ StatCollector.translateToLocalFormatted(
+ "GT5U.waila.energy.use_with_amperage",
+ GT_Utility.formatNumbers(actualEnergyUsage),
+ GT_Utility.getAmperageForTier(actualEnergyUsage, (byte) energyTier),
+ GT_Utility.getColoredTierNameFromTier((byte) energyTier)));
+ } else if (actualEnergyUsage < 0) {
+ currentTip.add(
+ StatCollector.translateToLocalFormatted(
+ "GT5U.waila.energy.produce_with_amperage",
+ GT_Utility.formatNumbers(-actualEnergyUsage),
+ GT_Utility.getAmperageForTier(-actualEnergyUsage, (byte) energyTier),
+ GT_Utility.getColoredTierNameFromTier((byte) energyTier)));
+ }
+ }
+ }
+
+ @Override
+ public GT_Packet_MultiTileEntity getClientDataPacket() {
+ final GT_Packet_MultiTileEntity packet = super.getClientDataPacket();
+
+ return packet;
+
+ }
+
+ @Override
+ public void enableWorking() {
+ super.enableWorking();
+ if (!structureOkay) {
+ checkStructure(true);
+ }
+ }
+
+ @Override
+ public List<ItemStack> getItemOutputSlots(ItemStack[] toOutput) {
+ return new ArrayList<>(0);
+ }
+
+ @Override
+ public List<? extends IFluidStore> getFluidOutputSlots(FluidStack[] toOutput) {
+ return new ArrayList<>(0);
+ }
+
+ @Override
+ @Nonnull
+ public Set<Entry<UUID, FluidInventoryLogic>> getAllFluidInventoryLogics(@Nonnull InventoryType type) {
+ return switch (type) {
+ case Input -> controllerFluidInput.getAllInventoryLogicsAsEntrySet();
+ case Output -> controllerFluidOutput.getAllInventoryLogicsAsEntrySet();
+ default -> super.getAllFluidInventoryLogics(type);
+ };
+ }
+
+ @Override
+ @Nonnull
+ public Set<Entry<UUID, ItemInventoryLogic>> getAllItemInventoryLogics(@Nonnull InventoryType type) {
+ return switch (type) {
+ case Input -> controllerItemInput.getAllInventoryLogicsAsEntrySet();
+ case Output -> controllerItemOutput.getAllInventoryLogicsAsEntrySet();
+ default -> super.getAllItemInventoryLogics(type);
+ };
+ }
+
+ @Override
+ public void setWirelessSupport(boolean canUse) {
+ if (canUse) {
+ strongCheckOrAddUser(getOwnerUuid());
+ }
+ power.setCanUseWireless(canUse, getOwnerUuid());
+ }
+
+ @Override
+ public void setLaserSupport(boolean canUse) {
+ power.setCanUseLaser(canUse);
+ }
+
+ @Override
+ public void setMaxAmperage(long amperage) {
+ power.setMaxAmperage(amperage);
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java
new file mode 100644
index 0000000000..5331d1477d
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java
@@ -0,0 +1,711 @@
+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.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;
+import static gregtech.api.enums.Textures.BlockIcons.ITEM_OUT_SIGN;
+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 java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Arrays;
+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;
+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 com.gtnewhorizons.modularui.api.screen.ModularWindow.Builder;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.common.widget.DrawableWidget;
+
+import gregtech.api.enums.GT_Values.NBT;
+import gregtech.api.enums.InventoryType;
+import gregtech.api.fluid.FluidTankGT;
+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;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_HasModes;
+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_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];
+
+ protected final List<Integer> BASIC_MODES = new ArrayList<>(
+ Arrays.asList(NOTHING, ENERGY_IN, ENERGY_OUT, FLUID_IN, FLUID_OUT, ITEM_IN, ITEM_OUT));
+
+ protected Set<MultiTileCasingPurpose> registeredPurposes = new HashSet<>();
+
+ protected ChunkCoordinates targetPosition = null;
+
+ 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.
+ */
+ public int getPartTier() {
+ return 1;
+ }
+
+ @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);
+ }
+ }
+ targetPosition = (newTarget == null ? null : newTarget.getCoords());
+ allowedModes = aAllowedModes;
+ if (newTarget != null) {
+ registerCovers(newTarget);
+ registerPurposes();
+ }
+ }
+
+ 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
+ protected void addDebugInfo(EntityPlayer aPlayer, int aLogLevel, ArrayList<String> tList) {
+ final IMultiBlockController controller = getTarget(false);
+ if (controller != null) {
+ tList.add("Has controller");
+ } else {
+ tList.add("No Controller");
+ }
+ 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(mode)));
+ if (modeSelected(FLUID_OUT)) {
+ if (configurationTank != null && configurationTank.get() != null) {
+ currentTip.add(
+ String.format(
+ "Locked to: %s",
+ configurationTank.get()
+ .getLocalizedName()));
+ } else {
+ currentTip.add("Locked to: Nothing");
+ }
+ }
+ }
+
+ public IMultiBlockController getTarget(boolean aCheckValidity) {
+ 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;
+ }
+ return target;
+ }
+
+ public void registerCovers(IMultiBlockController controller) {
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ final CoverInfo coverInfo = getCoverInfoAtSide(side);
+ if (coverInfo.isValid() && coverInfo.getTickRate() > 0) {
+ controller.registerCoveredPartOnSide(side, this);
+ }
+ }
+ }
+
+ 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) {
+ return;
+ }
+
+ final CoverInfo coverInfo = getCoverInfoAtSide(side);
+ if (coverInfo.isValid() && coverInfo.getTickRate() > 0) {
+ tTarget.registerCoveredPartOnSide(side, this);
+ }
+
+ }
+
+ public void unregisterCovers(IMultiBlockController controller) {
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ if (getCoverInfoAtSide(side).isValid()) {
+ controller.unregisterCoveredPartOnSide(side, this);
+ }
+ }
+ }
+
+ @Override
+ public boolean dropCover(ForgeDirection side, ForgeDirection droppedSide, boolean aForced) {
+ final boolean res = super.dropCover(side, droppedSide, aForced);
+ final IMultiBlockController tTarget = getTarget(true);
+ if (tTarget != null) {
+ tTarget.unregisterCoveredPartOnSide(side, this);
+ }
+ return res;
+ }
+
+ @Override
+ public void readMultiTileNBT(NBTTagCompound aNBT) {
+ 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)) {
+ targetPosition = new ChunkCoordinates(
+ aNBT.getInteger(NBT.TARGET_X),
+ aNBT.getShort(NBT.TARGET_Y),
+ aNBT.getInteger(NBT.TARGET_Z));
+ }
+ if (aNBT.hasKey(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);
+ }
+ 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 (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, targetPosition.posX);
+ aNBT.setShort(NBT.TARGET_Y, (short) targetPosition.posY);
+ aNBT.setInteger(NBT.TARGET_Z, targetPosition.posZ);
+ }
+ if (lockedInventory != null) {
+ aNBT.setString(NBT.LOCKED_INVENTORY, lockedInventory.toString());
+ }
+ if (mLockedInventoryIndex != 0) {
+ aNBT.setInteger(NBT.LOCKED_INVENTORY_INDEX, mLockedInventoryIndex);
+ }
+ configurationTank.writeToNBT(aNBT, NBT.LOCKED_FLUID);
+ }
+
+ @Override
+ public void setLockedInventoryIndex(int aIndex) {
+ mLockedInventoryIndex = aIndex;
+ }
+
+ @Override
+ public int getLockedInventoryIndex() {
+ return mLockedInventoryIndex;
+ }
+
+ @Override
+ public void setTargetPos(ChunkCoordinates aTargetPos) {
+ targetPosition = aTargetPos;
+ IMultiBlockController target = getTarget(false);
+ setTarget(target, allowedModes);
+ }
+
+ @Override
+ public ChunkCoordinates getTargetPos() {
+ return targetPosition;
+ }
+
+ @Override
+ 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 int getMode() {
+ return mode;
+ }
+
+ @Override
+ public int getAllowedModes() {
+ return allowedModes;
+ }
+
+ @Override
+ public void setAllowedModes(int aAllowedModes) {
+ allowedModes = aAllowedModes;
+ }
+
+ /**
+ * True if `aMode` is one of the allowed modes
+ */
+ public boolean hasMode(int aMode) {
+ // This is not sent to the client
+ return (allowedModes & aMode) != 0;
+ }
+
+ /**
+ * Returns true if the part has any of the modes provided, and that mode is the currently selected mode
+ */
+ public boolean modeSelected(int... aModes) {
+ for (int aMode : aModes) {
+ if (hasMode(aMode) && mode == getModeOrdinal(aMode)) return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean breakBlock() {
+ final IMultiBlockController tTarget = getTarget(false);
+ if (tTarget != null) {
+ unregisterCovers(tTarget);
+ tTarget.onStructureChange();
+ }
+ return false;
+ }
+
+ @Override
+ public void onBlockAdded() {
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ final TileEntity te = getTileEntityAtSide(side);
+ if (te instanceof MultiBlockPart part) {
+ final IMultiBlockController tController = part.getTarget(false);
+ if (tController != null) tController.onStructureChange();
+ } else if (te instanceof IMultiBlockController controller) {
+ controller.onStructureChange();
+ }
+ }
+ }
+
+ @Override
+ public ITexture getTexture(ForgeDirection side) {
+ ITexture texture = super.getTexture(side);
+ 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 (mode == getModeOrdinal(ITEM_OUT)) {
+ return TextureFactory.of(
+ texture,
+ TextureFactory.of(OVERLAY_PIPE_OUT),
+ TextureFactory.of(ITEM_OUT_SIGN),
+ getCoverTexture(side));
+ }
+ if (mode == getModeOrdinal(FLUID_IN)) {
+ return TextureFactory.of(
+ texture,
+ TextureFactory.of(OVERLAY_PIPE_IN),
+ TextureFactory.of(FLUID_IN_SIGN),
+ getCoverTexture(side));
+ }
+ if (mode == getModeOrdinal(FLUID_OUT)) {
+ return TextureFactory.of(
+ texture,
+ TextureFactory.of(OVERLAY_PIPE_OUT),
+ TextureFactory.of(FLUID_OUT_SIGN),
+ getCoverTexture(side));
+ }
+ if (mode == getModeOrdinal(ENERGY_IN)) {
+ return TextureFactory.of(texture, TextureFactory.of(OVERLAY_ENERGY_IN_MULTI), getCoverTexture(side));
+ }
+ if (mode == getModeOrdinal(ENERGY_OUT)) {
+ return TextureFactory.of(texture, TextureFactory.of(OVERLAY_ENERGY_OUT_MULTI), getCoverTexture(side));
+ }
+ }
+
+ return TextureFactory.of(texture, getCoverTexture(side));
+ }
+
+ protected String getModeName(int aMode) {
+ if (aMode == NOTHING) return "Nothing";
+ if (aMode == getModeOrdinal(ITEM_IN)) return "Item Input";
+ if (aMode == getModeOrdinal(ITEM_OUT)) return "Item Output";
+ if (aMode == getModeOrdinal(FLUID_IN)) return "Fluid Input";
+ if (aMode == getModeOrdinal(FLUID_OUT)) return "Fluid Output";
+ if (aMode == getModeOrdinal(ENERGY_IN)) return "Energy Input";
+ if (aMode == getModeOrdinal(ENERGY_OUT)) return "Energy Output";
+ return "Unknown";
+ }
+
+ protected byte getModeOrdinal(int aMode) {
+ // log2 returns the bit position of the only bit set, add 1 to account for 0 being NOTHING
+ // NOTE: Must be a power of 2 (single bit)
+ return (byte) (log2(aMode, RoundingMode.UNNECESSARY) + 1);
+ }
+
+ protected byte getNextAllowedMode(List<Integer> allowedModes) {
+ if (this.allowedModes == NOTHING) return NOTHING;
+
+ final int numModes = allowedModes.size();
+ for (byte i = 1; i <= numModes; i++) {
+ final byte curMode = (byte) ((mode + i) % numModes);
+ if (curMode == NOTHING || hasMode(1 << (curMode - 1))) return curMode;
+ }
+ // Nothing valid found
+ return 0;
+ }
+
+ @Override
+ public boolean onMalletRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
+ float aY, float aZ) {
+ if (allowedModes == NOTHING) return true;
+ if (mode == NOTHING) {
+ facing = wrenchSide;
+ }
+ setMode(getNextAllowedMode(BASIC_MODES));
+ if (aPlayer.isSneaking()) {
+ facing = wrenchSide;
+ }
+ GT_Utility.sendChatToPlayer(aPlayer, "Mode set to `" + getModeName(mode) + "' (" + mode + ")");
+ sendClientData((EntityPlayerMP) aPlayer);
+ return true;
+ }
+
+ @Override
+ public void setLightValue(byte aLightValue) {}
+
+ @Override
+ public byte getComparatorValue(ForgeDirection side) {
+ return 0;
+ }
+
+ @Override
+ public String getTileEntityName() {
+ 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
+ */
+
+ // #region Fluid - Depending on the part type - proxy it to the multiblock controller, if we have one
+ @Override
+ @Nullable
+ public FluidInventoryLogic getFluidLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type) {
+ if (side != facing && side != ForgeDirection.UNKNOWN) return null;
+
+ if (!modeSelected(FLUID_IN, FLUID_OUT)) return null;
+
+ IMultiBlockController controller = getTarget(false);
+ if (controller == null) return null;
+ return controller
+ .getFluidLogic(modeSelected(FLUID_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory);
+ }
+
+ // #endregion Fluid
+
+ // #region Energy - Depending on the part type - proxy to the multiblock controller, if we have one
+
+ @Override
+ @Nonnull
+ public PowerLogic getPowerLogic(@Nonnull ForgeDirection side) {
+ if (side != facing && side != ForgeDirection.UNKNOWN) {
+ return new NullPowerLogic();
+ }
+
+ if (!modeSelected(ENERGY_IN, ENERGY_OUT)) {
+ return new NullPowerLogic();
+ }
+
+ final IMultiBlockController controller = getTarget(true);
+ if (controller == null) {
+ return new NullPowerLogic();
+ }
+ return controller.getPowerLogic();
+ }
+
+ @Override
+ public boolean isEnetInput() {
+ return modeSelected(ENERGY_IN);
+ }
+
+ @Override
+ public boolean isEnetOutput() {
+ return modeSelected(ENERGY_OUT);
+ }
+
+ // #endregion Energy
+
+ // #region Item - Depending on the part type - proxy to the multiblock controller, if we have one
+
+ @Override
+ @Nullable
+ public ItemInventoryLogic getItemLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType unused) {
+ if (side != facing && side != ForgeDirection.UNKNOWN) return null;
+
+ if (!modeSelected(ITEM_IN, ITEM_OUT)) return null;
+
+ final IMultiBlockController controller = getTarget(false);
+ if (controller == null) return null;
+
+ return controller
+ .getItemLogic(modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory);
+ }
+
+ @Override
+ @Nullable
+ public InventoryType getItemInventoryType() {
+ if (!modeSelected(ITEM_IN, ITEM_OUT)) return InventoryType.Both;
+ return modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output;
+ }
+
+ // #endregion Item
+
+ // === Modular UI ===
+ @Override
+ public boolean useModularUI() {
+ return true;
+ }
+
+ @Override
+ public String getLocalName() {
+ if (modeSelected(ITEM_IN)) return "Input Inventory";
+ if (modeSelected(ITEM_OUT)) return "Output Inventory";
+ if (modeSelected(FLUID_IN)) return "Fluid Input Hatch";
+ if (modeSelected(FLUID_OUT)) return "Fluid Output Hatch";
+
+ return "Unknown";
+ }
+
+ @Override
+ public boolean hasGui(ForgeDirection side) {
+ if (modeSelected(ENERGY_IN, ENERGY_OUT) && facing == side) {
+ return false;
+ }
+ return getTarget(true) != null;
+ }
+
+ protected boolean isWrongFluid(Fluid fluid) {
+ if (fluid == null) {
+ return true;
+ }
+ Fluid lockedFluid = getLockedFluid();
+ if (lockedFluid != null) {
+ return !fluid.equals(lockedFluid);
+ }
+ return false;
+ }
+
+ protected Fluid getLockedFluid() {
+ if (configurationTank.get() != null && configurationTank.get()
+ .getFluid() != null) {
+ return configurationTank.get()
+ .getFluid();
+ }
+ return null;
+ }
+
+ @Override
+ public void addUIWidgets(Builder builder, UIBuildContext buildContext) {
+ super.addUIWidgets(builder, buildContext);
+ IMultiBlockController controller = getTarget(false);
+ if (controller == null) {
+ return;
+ }
+ if ((modeSelected(ITEM_IN, ITEM_OUT))) {
+ builder.widget(
+ controller
+ .getItemLogic(modeSelected(ITEM_IN) ? InventoryType.Input : InventoryType.Output, lockedInventory)
+ .getGuiPart()
+ .setSize(18 * 4 + 4, 18 * 5)
+ .setPos(52, 7));
+ }
+
+ 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));
+ }
+ }
+
+ protected boolean canOpenControllerGui() {
+ return true;
+ }
+
+ @Override
+ protected int getGUIHeight() {
+ return super.getGUIHeight() + 20;
+ }
+
+ @Override
+ public void addGregTechLogo(Builder builder) {
+ if (modeSelected(ITEM_IN, ITEM_OUT)) {
+ builder.widget(
+ new DrawableWidget().setDrawable(getGUITextureSet().getGregTechLogo())
+ .setSize(17, 17)
+ .setPos(152, 74));
+ } else if (modeSelected(FLUID_IN, FLUID_OUT)) {
+ builder.widget(
+ new DrawableWidget().setDrawable(getGUITextureSet().getGregTechLogo())
+ .setSize(17, 17)
+ .setPos(152, 82));
+ } else {
+ 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());
+ }
+
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/StackableController.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/StackableController.java
new file mode 100644
index 0000000000..51feb363dd
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/StackableController.java
@@ -0,0 +1,128 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import net.minecraft.item.ItemStack;
+
+import com.gtnewhorizon.structurelib.util.Vec3Impl;
+
+import gregtech.api.logic.MuTEProcessingLogic;
+
+public abstract class StackableController<C extends StackableController<C, P>, P extends MuTEProcessingLogic<P>>
+ extends Controller<C, P> {
+
+ protected static String STACKABLE_STOP = "STACKABLE_STOP";
+ protected static String STACKABLE_MIDDLE = "STACKABLE_MIDDLE";
+ protected static String STACKABLE_START = "STACKABLE_START";
+ protected int stackCount = 0;
+
+ /**
+ * construct implementation for stackable multi-blocks
+ */
+ @Override
+ public void construct(ItemStack trigger, boolean hintsOnly) {
+ final int blueprintCount = (trigger.stackSize - 1) + getMinStacks();
+ final int stackCount = Math.min(blueprintCount, getMaxStacks());
+
+ buildState.startBuilding(getStartingStructureOffset());
+ buildPiece(getStackableStart(), trigger, hintsOnly, buildState.getCurrentOffset());
+ buildState.addOffset(getStartingStackOffset());
+
+ for (int i = 0; i < stackCount; i++) {
+ buildPiece(getStackableMiddle(i), trigger, hintsOnly, buildState.getCurrentOffset());
+ buildState.addOffset(getPerStackOffset());
+ }
+ if (hasTop()) {
+ buildState.addOffset(getAfterLastStackOffset());
+ buildPiece(getStackableStop(), trigger, hintsOnly, buildState.stopBuilding());
+ } else {
+ buildState.stopBuilding();
+ }
+ }
+
+ /**
+ * Stackable
+ *
+ * @return The minimum number of stacks required for this multi-block to form
+ */
+ public abstract int getMinStacks();
+
+ /**
+ * Stackable
+ *
+ * @return The maximum number of stacks allowed for this multi-block to form
+ */
+ public abstract int getMaxStacks();
+
+ /**
+ * Stackable
+ *
+ * @return The starting offset for the first stack
+ */
+ public abstract Vec3Impl getStartingStackOffset();
+
+ /**
+ * Stackable
+ *
+ * @return The per stack offset
+ */
+ public abstract Vec3Impl getPerStackOffset();
+
+ /**
+ * Stackable
+ *
+ * @return Whether this structure has a Top/Cap. Defaults to true.
+ */
+ public boolean hasTop() {
+ return true;
+ }
+
+ /**
+ * Stackable
+ *
+ * @return Any offset needed after the last stack
+ */
+ public Vec3Impl getAfterLastStackOffset() {
+ return new Vec3Impl(0, 0, 0);
+ }
+
+ /**
+ * checkMachine implementation for stackable multi-blocks
+ */
+ @Override
+ public boolean checkMachine() {
+ stackCount = 0;
+
+ buildState.startBuilding(getStartingStructureOffset());
+ if (!checkPiece(getStackableStart(), buildState.getCurrentOffset())) return buildState.failBuilding();
+
+ buildState.addOffset(getStartingStackOffset());
+
+ for (int i = 0; i < getMaxStacks(); i++) {
+ if (!checkPiece(getStackableMiddle(i), buildState.getCurrentOffset())) {
+ break;
+ }
+
+ buildState.addOffset(getPerStackOffset());
+ stackCount++;
+
+ }
+ if (stackCount < getMinStacks()) return buildState.failBuilding();
+
+ buildState.addOffset(getAfterLastStackOffset());
+ if (!checkPiece(getStackableStop(), buildState.stopBuilding())) {
+ return buildState.failBuilding();
+ }
+ return super.checkMachine();
+ }
+
+ protected String getStackableStop() {
+ return STACKABLE_STOP;
+ }
+
+ protected String getStackableMiddle(int stackIndex) {
+ return STACKABLE_MIDDLE;
+ }
+
+ protected String getStackableStart() {
+ return STACKABLE_START;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/StackableModularController.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/StackableModularController.java
new file mode 100644
index 0000000000..1dfd497151
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/StackableModularController.java
@@ -0,0 +1,77 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jetbrains.annotations.NotNull;
+
+import gregtech.api.logic.MuTEProcessingLogic;
+import gregtech.api.multitileentity.interfaces.UpgradableModularMuTE;
+import gregtech.api.util.GT_StructureUtilityMuTE.UpgradeCasings;
+
+public abstract class StackableModularController<C extends StackableModularController<C, P>, P extends MuTEProcessingLogic<P>>
+ extends StackableController<C, P> implements UpgradableModularMuTE {
+
+ protected double durationMultiplier = 1;
+ protected double euTickMultiplier = 1;
+
+ private Map<UpgradeCasings, int[]> mucMap;
+
+ protected @NotNull Map<UpgradeCasings, int[]> getMucMap() {
+ if (mucMap == null) {
+ mucMap = createMucMap();
+ }
+ return mucMap;
+ }
+
+ protected static @NotNull Map<UpgradeCasings, int[]> createMucMap() {
+ Map<UpgradeCasings, int[]> mucCount = new HashMap<>();
+ mucCount.put(UpgradeCasings.Heater, new int[] { 0, 0, 0, 0, 0 });
+ mucCount.put(UpgradeCasings.Insulator, new int[] { 0, 0, 0, 0, 0 });
+ return mucCount;
+ }
+
+ @Override
+ public void increaseMucCount(UpgradeCasings casingType, int tier) {
+ Map<UpgradeCasings, int[]> mucCounters = getMucMap();
+ int[] casingCount = mucCounters.get(casingType);
+
+ switch (tier) {
+ case 0, 1, 2 -> casingCount[0] += 1;
+ case 3, 4, 5 -> casingCount[1] += 1;
+ case 6, 7, 8 -> casingCount[2] += 1;
+ case 9, 10, 11 -> casingCount[3] += 1;
+ default -> casingCount[4] += 1;
+ }
+ }
+
+ @Override
+ public void resetMucCount() {
+ Map<UpgradeCasings, int[]> mucCounters = getMucMap();
+ mucCounters.forEach((type, casingCount) -> { Arrays.fill(casingCount, 0); });
+ }
+
+ // Returns the cheapest MUC that is possible for the multi, which gets the minimum bonuses.
+ protected abstract UpgradeCasings getBaseMucType();
+
+ // Minimum parallel bonus per MUC. Higher tier MUCs multiply with this value for even more parallels.
+ protected abstract int getParallelFactor();
+
+ protected void calculateParallels() {
+ int parallelCount = 0;
+ int parallelFactor = getParallelFactor();
+ int[] parallelCasingList = mucMap.get(getBaseMucType());
+
+ for (int i = 0; i < 5; i++) {
+ // (i * 3 + 1) -> Convert MUC tier into minimum GT tier, in groups of 3 (LV, EV, LuV, UHV, UMV)
+ // If higher than multi tier, upgrade casing has no effect
+ if (i * 3 + 1 <= tier) {
+ parallelCount += parallelCasingList[i] * (i + 1) * parallelFactor;
+ }
+ }
+ maxParallel = parallelCount == 0 ? 1 : parallelCount;
+ }
+
+ protected abstract boolean calculateMucMultipliers();
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/WallShareablePart.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/WallShareablePart.java
new file mode 100644
index 0000000000..ccde0c49e6
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/WallShareablePart.java
@@ -0,0 +1,96 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.ChunkCoordinates;
+import net.minecraftforge.common.util.ForgeDirection;
+
+import gregtech.api.multitileentity.interfaces.IMultiBlockController;
+
+public class WallShareablePart extends MultiBlockPart {
+
+ protected List<ChunkCoordinates> targetPositions = new ArrayList<>();
+
+ @Override
+ public void setTarget(IMultiBlockController aTarget, int aAllowedModes) {
+ if (targetPositions.size() >= 1) {
+ allowedModes = 0;
+ setMode((byte) 0);
+ targetPosition = null;
+ } else {
+ allowedModes = aAllowedModes;
+ }
+
+ if (aTarget == null) {
+ return;
+ }
+
+ targetPositions.add(aTarget.getCoords());
+ }
+
+ @Override
+ public UUID getLockedInventory() {
+ if (targetPositions.size() > 1) {
+ return null;
+ }
+ return super.getLockedInventory();
+ }
+
+ @Override
+ public IMultiBlockController getTarget(boolean aCheckValidity) {
+ if (targetPositions.size() != 1) {
+ return null;
+ }
+
+ targetPosition = targetPositions.get(0);
+ return super.getTarget(aCheckValidity);
+ }
+
+ @Override
+ public String getTileEntityName() {
+ return "gt.multiTileEntity.casing.wallSharable";
+ }
+
+ @Override
+ public boolean breakBlock() {
+ for (final ChunkCoordinates coordinates : targetPositions) {
+ IMultiBlockController target = getTarget(coordinates, false);
+ if (target == null) {
+ continue;
+ }
+ target.onStructureChange();
+ }
+ return false;
+ }
+
+ @Override
+ public void onBlockAdded() {
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ final TileEntity te = getTileEntityAtSide(side);
+ if (te instanceof MultiBlockPart part) {
+ final IMultiBlockController tController = part.getTarget(false);
+ if (tController != null) tController.onStructureChange();
+ } else if (te instanceof IMultiBlockController controller) {
+ controller.onStructureChange();
+ }
+ }
+ }
+
+ public IMultiBlockController getTarget(ChunkCoordinates coordinates, boolean aCheckValidity) {
+ IMultiBlockController target = null;
+ if (coordinates == null) return null;
+ if (worldObj.blockExists(coordinates.posX, coordinates.posY, coordinates.posZ)) {
+ final TileEntity te = worldObj.getTileEntity(coordinates.posX, coordinates.posY, coordinates.posZ);
+ if (te instanceof IMultiBlockController) {
+ target = (IMultiBlockController) te;
+ }
+ }
+ if (aCheckValidity) {
+ return target != null && target.checkStructure(false) ? target : null;
+ }
+ return target;
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/casing/BasicCasing.java b/src/main/java/gregtech/api/multitileentity/multiblock/casing/BasicCasing.java
new file mode 100644
index 0000000000..84f1442a88
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/casing/BasicCasing.java
@@ -0,0 +1,7 @@
+package gregtech.api.multitileentity.multiblock.casing;
+
+import gregtech.api.multitileentity.multiblock.base.MultiBlockPart;
+
+public class BasicCasing extends MultiBlockPart {
+ /* Nothing */
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/casing/CasingBehaviorBase.java b/src/main/java/gregtech/api/multitileentity/multiblock/casing/CasingBehaviorBase.java
new file mode 100644
index 0000000000..9f0d9bd2d1
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/casing/CasingBehaviorBase.java
@@ -0,0 +1,10 @@
+package gregtech.api.multitileentity.multiblock.casing;
+
+/**
+ * Allows for functional casings that influence the multiblock structure's behavior Examples include: - Extra Byproducts
+ * - Extra Speed - More parallels - Upgraded internal item/energy/fluid storage - Faster output - Voiding/Anti-Voiding
+ * upgrade - Ender Upgrades - etc, etc.
+ *
+ */
+public class CasingBehaviorBase {
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/casing/FunctionalCasing.java b/src/main/java/gregtech/api/multitileentity/multiblock/casing/FunctionalCasing.java
new file mode 100644
index 0000000000..bc3c857fd6
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/casing/FunctionalCasing.java
@@ -0,0 +1,29 @@
+package gregtech.api.multitileentity.multiblock.casing;
+
+import net.minecraft.nbt.NBTTagCompound;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.multitileentity.multiblock.base.MultiBlockPart;
+
+public abstract class FunctionalCasing extends MultiBlockPart {
+
+ private int tier = 0;
+
+ @Override
+ public int getPartTier() {
+ return tier;
+ }
+
+ public abstract float getPartModifier();
+
+ @Override
+ public void readMultiTileNBT(NBTTagCompound nbt) {
+ super.readMultiTileNBT(nbt);
+ tier = nbt.getInteger(GT_Values.NBT.TIER);
+ }
+
+ @Override
+ public void writeMultiTileNBT(NBTTagCompound nbt) {
+ super.writeMultiTileNBT(nbt);
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/casing/Glasses.java b/src/main/java/gregtech/api/multitileentity/multiblock/casing/Glasses.java
new file mode 100644
index 0000000000..edc1bd0e5b
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/casing/Glasses.java
@@ -0,0 +1,32 @@
+package gregtech.api.multitileentity.multiblock.casing;
+
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofBlockUnlocalizedName;
+import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain;
+import static gregtech.api.enums.Mods.BartWorks;
+import static gregtech.api.enums.Mods.Botania;
+import static gregtech.api.enums.Mods.IndustrialCraft2;
+import static gregtech.api.enums.Mods.Thaumcraft;
+
+import com.gtnewhorizon.structurelib.structure.IStructureElementChain;
+
+public class Glasses {
+
+ /** support all Bart, Botania, Ic2, Thaumcraft glasses for multiblock structure **/
+ public static <T> IStructureElementChain<T> chainAllGlasses() {
+ return ofChain(
+ // IndustrialCraft2 glass
+ ofBlockUnlocalizedName(IndustrialCraft2.ID, "blockAlloyGlass", 0, true),
+
+ // Botania glass
+ ofBlockUnlocalizedName(Botania.ID, "manaGlass", 0, false),
+ ofBlockUnlocalizedName(Botania.ID, "elfGlass", 0, false),
+
+ // BartWorks glass
+ ofBlockUnlocalizedName(BartWorks.ID, "BW_GlasBlocks", 0, true),
+ ofBlockUnlocalizedName(BartWorks.ID, "BW_GlasBlocks2", 0, true),
+
+ // warded glass
+ ofBlockUnlocalizedName(Thaumcraft.ID, "blockCosmeticOpaque", 2, false));
+ }
+
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/casing/UpgradeCasing.java b/src/main/java/gregtech/api/multitileentity/multiblock/casing/UpgradeCasing.java
new file mode 100644
index 0000000000..fb045557e4
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/casing/UpgradeCasing.java
@@ -0,0 +1,35 @@
+package gregtech.api.multitileentity.multiblock.casing;
+
+import net.minecraft.nbt.NBTTagCompound;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.multitileentity.interfaces.IMultiBlockController;
+import gregtech.api.multitileentity.multiblock.base.MultiBlockPart;
+
+public abstract class UpgradeCasing extends MultiBlockPart {
+
+ protected int tier = 0;
+
+ @Override
+ public int getPartTier() {
+ return tier;
+ }
+
+ @Override
+ public void setTarget(IMultiBlockController newTarget, int aAllowedModes) {
+ super.setTarget(newTarget, aAllowedModes);
+
+ if (getTarget(false) != null) {
+ customWork(getTarget(false));
+ }
+ }
+
+ @Override
+ public void readMultiTileNBT(NBTTagCompound aNBT) {
+ super.readMultiTileNBT(aNBT);
+ tier = aNBT.getInteger(GT_Values.NBT.TIER);
+ }
+
+ protected abstract void customWork(IMultiBlockController aTarget);
+
+}