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/interfaces/IMultiBlockController.java19
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockEnergy.java23
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockFluidHandler.java16
-rw-r--r--src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockInventory.java28
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockController.java860
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java600
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlock_Stackable.java114
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/CasingBehaviorBase.java17
8 files changed, 1677 insertions, 0 deletions
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..95b369847f
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockController.java
@@ -0,0 +1,19 @@
+package gregtech.api.multitileentity.interfaces;
+
+import net.minecraft.util.ChunkCoordinates;
+import net.minecraftforge.fluids.FluidStack;
+
+public interface IMultiBlockController extends IMultiTileEntity, IMultiBlockFluidHandler, IMultiBlockInventory, IMultiBlockEnergy {
+ boolean checkStructure(boolean aForceReset);
+
+ /** Set the structure as having changed, and trigger an update */
+ void onStructureChange();
+
+ @Override ChunkCoordinates getCoords();
+
+ FluidStack getDrainableFluid(byte aSide);
+
+ boolean isLiquidInput(byte aSide);
+ boolean isLiquidOutput(byte aSide);
+
+}
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..3e9892d3bd
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockEnergy.java
@@ -0,0 +1,23 @@
+package gregtech.api.multitileentity.interfaces;
+
+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, byte aSide, long aVoltage, long aAmperage);
+ long injectEnergyUnits (MultiBlockPart aPart, byte aSide, long aVoltage, long aAmperage);
+ long getAverageElectricInput (MultiBlockPart aPart);
+ long getAverageElectricOutput (MultiBlockPart aPart);
+ long getStoredEU (MultiBlockPart aPart);
+ long getEUCapacity (MultiBlockPart aPart);
+ boolean inputEnergyFrom (MultiBlockPart aPart, byte aSide);
+ boolean outputsEnergyTo (MultiBlockPart aPart, byte aSide);
+}
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..4935ad49ab
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockFluidHandler.java
@@ -0,0 +1,16 @@
+package gregtech.api.multitileentity.interfaces;
+
+import gregtech.api.multitileentity.multiblock.base.MultiBlockPart;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.FluidTankInfo;
+
+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);
+}
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..4df5a11cc0
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockInventory.java
@@ -0,0 +1,28 @@
+package gregtech.api.multitileentity.interfaces;
+
+import gregtech.api.multitileentity.multiblock.base.MultiBlockPart;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+
+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, byte aSide);
+ boolean canInsertItem (MultiBlockPart aPart, int aSlot, ItemStack aStack, byte aSide);
+ boolean canExtractItem (MultiBlockPart aPart, int aSlot, ItemStack aStack, byte aSide);
+ 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);
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockController.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockController.java
new file mode 100644
index 0000000000..afd8f9577f
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockController.java
@@ -0,0 +1,860 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import com.gtnewhorizon.structurelib.StructureLibAPI;
+import com.gtnewhorizon.structurelib.alignment.IAlignment;
+import com.gtnewhorizon.structurelib.alignment.IAlignmentLimits;
+import com.gtnewhorizon.structurelib.alignment.constructable.IConstructable;
+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.IStructureElement;
+import com.gtnewhorizon.structurelib.util.Vec3Impl;
+import cpw.mods.fml.common.network.NetworkRegistry;
+import gnu.trove.list.TIntList;
+import gnu.trove.list.array.TIntArrayList;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.enums.OrePrefixes;
+import gregtech.api.enums.TextureSet;
+import gregtech.api.interfaces.IDescribable;
+import gregtech.api.interfaces.tileentity.IMachineProgress;
+import gregtech.api.multitileentity.MultiTileEntityContainer;
+import gregtech.api.multitileentity.MultiTileEntityRegistry;
+import gregtech.api.multitileentity.interfaces.IMultiBlockController;
+import gregtech.api.multitileentity.interfaces.IMultiBlockFluidHandler;
+import gregtech.api.multitileentity.interfaces.IMultiBlockInventory;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_AddToolTips;
+import gregtech.api.multitileentity.machine.MultiTileBasicMachine;
+import gregtech.api.objects.GT_ItemStack;
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import gregtech.api.util.GT_Utility;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.IIcon;
+import net.minecraft.util.Tuple;
+import net.minecraft.world.World;
+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 org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.lwjgl.input.Keyboard;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static gregtech.GT_Mod.GT_FML_LOGGER;
+import static gregtech.api.enums.GT_Values.NBT;
+
+public abstract class MultiBlockController<T extends MultiBlockController<T>> extends MultiTileBasicMachine
+ implements IAlignment, IConstructable, IMultiBlockController, IDescribable, IMachineProgress, IMultiBlockFluidHandler, IMultiBlockInventory, IMTE_AddToolTips
+{
+ private static final Map<Integer, GT_Multiblock_Tooltip_Builder> tooltip = new ConcurrentHashMap<>();
+
+ protected BuildState buildState = new BuildState();
+
+ // The 0th slot is the default inventory of the MultiBlock; any other has been added by an Inventory Extender of sorts
+ protected List<ItemStack[]> multiBlockInventory = new ArrayList<>();
+
+
+ private int mMaxProgresstime = 0, mProgresstime = 0;
+ private boolean mStructureOkay = false, mStructureChanged = false;
+ private boolean mWorks = true, mWorkUpdate = false, mWasShutdown = false, mActive = false;
+ private ExtendedFacing mExtendedFacing = ExtendedFacing.DEFAULT;
+ private IAlignmentLimits mLimits = getInitialAlignmentLimits();
+
+ /** Registry ID of the required casing */
+ public abstract short getCasingRegistryID();
+ /** Meta ID of the required casing */
+ public abstract short 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.
+ */
+ public abstract IStructureDefinition<T> getStructureDefinition();
+
+ /**
+ * Checks the Machine.
+ * <p>
+ * NOTE: If using `buildState` be sure to `startBuilding()` and either `endBuilding()` or `failBuilding()`
+ */
+ public abstract boolean checkMachine();
+
+ /**
+ * Checks the Recipe
+ */
+ public abstract boolean checkRecipe(ItemStack aStack);
+
+
+ @Override
+ public void writeMultiTileNBT(NBTTagCompound aNBT) {
+ super.writeMultiTileNBT(aNBT);
+
+ aNBT.setBoolean(NBT.STRUCTURE_OK, mStructureOkay);
+ aNBT.setByte(NBT.ROTATION, (byte) mExtendedFacing.getRotation().getIndex());
+ aNBT.setByte(NBT.FLIP, (byte) mExtendedFacing.getFlip().getIndex());
+ }
+
+ @Override
+ public void readMultiTileNBT(NBTTagCompound aNBT) {
+ super.readMultiTileNBT(aNBT);
+
+ // Multiblock inventories are a collection of inventories. The first inventory is the default internal inventory,
+ // and the others are added by inventory extending blocks.
+ if(mInventory != null) multiBlockInventory.add(mInventory);
+
+ mStructureOkay = aNBT.getBoolean(NBT.STRUCTURE_OK);
+ mExtendedFacing = ExtendedFacing.of(
+ ForgeDirection.getOrientation(getFrontFacing()),
+ Rotation.byIndex(aNBT.getByte(NBT.ROTATION)),
+ Flip.byIndex(aNBT.getByte(NBT.FLIP))
+ );
+ }
+
+ @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();
+ } else {
+ return getTooltip().getInformation();
+ }
+ }
+
+ @Override
+ protected void addDebugInfo(EntityPlayer aPlayer, int aLogLevel, ArrayList<String> tList) {
+ tList.add("Structure ok: " + checkStructure(false));
+ }
+
+ protected int getToolTipID() {
+ return getMultiTileEntityRegistryID() << 16 + getMultiTileEntityID();
+ }
+
+ protected GT_Multiblock_Tooltip_Builder getTooltip() {
+ return createTooltip();
+// final int tooltipId = getToolTipID();
+// final GT_Multiblock_Tooltip_Builder tt = tooltip.get(tooltipId);
+// if (tt == null) {
+// return tooltip.computeIfAbsent(tooltipId, k -> createTooltip());
+// }
+// return tt;
+ }
+
+
+ @Override
+ public boolean checkStructure(boolean aForceReset) {
+ if(!isServerSide()) return mStructureOkay;
+
+ // Only trigger an update if forced (from onPostTick, generally), or if the structure has changed
+ if ((mStructureChanged || aForceReset)) {
+ mStructureOkay = checkMachine();
+ }
+ mStructureChanged = false;
+ return mStructureOkay;
+ }
+
+ @Override
+ public void onStructureChange() {
+ mStructureChanged = 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, !mStructureOkay
+ );
+ }
+
+ 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
+ );
+ }
+
+ @SuppressWarnings("unchecked")
+ private IStructureDefinition<MultiBlockController<T>> getCastedStructureDefinition() {
+ return (IStructureDefinition<MultiBlockController<T>>) getStructureDefinition();
+ }
+
+ @Override
+ public ExtendedFacing getExtendedFacing() {
+ return mExtendedFacing;
+ }
+
+ @Override
+ public void setExtendedFacing(ExtendedFacing newExtendedFacing) {
+ if (mExtendedFacing != newExtendedFacing) {
+ onStructureChange();
+ if (mStructureOkay)
+ stopMachine();
+ mExtendedFacing = newExtendedFacing;
+ mStructureOkay = 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, byte wrenchSide, float aX, float aY, float aZ) {
+ 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 onFirstTick(boolean aIsServerSide) {
+ super.onFirstTick(aIsServerSide);
+ if (aIsServerSide)
+ checkStructure(true);
+ else
+ StructureLibAPI.queryAlignment(this);
+ }
+
+ @Override
+ public void onPostTick(long aTick, boolean aIsServerSide) {
+ if (aIsServerSide) {
+ if (aTick % 600 == 5) {
+ // Recheck the structure every 30 seconds or so
+ if (!checkStructure(false)) checkStructure(true);
+ }
+ }
+ }
+
+ @Override
+ public final boolean isFacingValid(byte aFacing) {
+ return canSetToDirectionAny(ForgeDirection.getOrientation(aFacing));
+ }
+
+ @Override
+ public void onFacingChange() {
+ toolSetDirection(ForgeDirection.getOrientation(getFrontFacing()));
+ onStructureChange();
+ }
+
+ @Override
+ public boolean allowCoverOnSide(byte aSide, GT_ItemStack aCoverID) {
+ return aSide != mFacing;
+ }
+
+ @Override
+ public String[] getStructureDescription(ItemStack stackSize) {
+ return getTooltip().getStructureHint();
+ }
+
+ @Override
+ public IAlignmentLimits getAlignmentLimits() {
+ return mLimits;
+ }
+
+ protected void setAlignmentLimits(IAlignmentLimits mLimits) {
+ this.mLimits = mLimits;
+ }
+
+ // IMachineProgress
+ @Override
+ public int getProgress() {
+ return mProgresstime;
+ }
+
+ @Override
+ public int getMaxProgress() {
+ return mMaxProgresstime;
+ }
+
+ @Override
+ public boolean increaseProgress(int aProgressAmountInTicks) {
+ return increaseProgressGetOverflow(aProgressAmountInTicks) != aProgressAmountInTicks;
+ }
+
+ @Override
+ public FluidStack getDrainableFluid(byte aSide) {
+ final IFluidTank tank = getFluidTankDrainable(aSide, null);
+ return tank == null ? null : tank.getFluid();
+
+ }
+
+ /**
+ * Increases the Progress, returns the overflown Progress.
+ */
+ public int increaseProgressGetOverflow(int aProgress) {
+ return 0;
+ }
+
+ @Override
+ public boolean hasThingsToDo() {
+ return getMaxProgress() > 0;
+ }
+
+ @Override
+ public boolean hasWorkJustBeenEnabled() {
+ return mWorkUpdate;
+ }
+
+ @Override
+ public void enableWorking() {
+ if (!mWorks) mWorkUpdate = true;
+ mWorks = true;
+ mWasShutdown = false;
+ }
+
+ @Override
+ public void disableWorking() {
+ mWorks = false;
+ }
+
+ @Override
+ public boolean isAllowedToWork() {
+ return mWorks;
+ }
+
+ @Override
+ public boolean isActive() {
+ return mActive;
+ }
+
+ @Override
+ public void setActive(boolean aActive) {
+ mActive = aActive;
+ }
+
+ @Override
+ public boolean wasShutdown() {
+ return mWasShutdown;
+ }
+
+ // End IMachineProgress
+
+ public void stopMachine() {
+ disableWorking();
+ }
+
+ 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 <S> IStructureElement<S> addMultiTileCasing(int aRegistryID, int aBlockMeta, int aModes) {
+ return new IStructureElement<S>() {
+ private final short[] DEFAULT = new short[]{255, 255, 255, 0};
+ private IIcon[] mIcons = null;
+
+ @Override
+ public boolean check(S t, World world, int x, int y, int z) {
+ final TileEntity tileEntity = world.getTileEntity(x, y, z);
+ if (!(tileEntity instanceof MultiBlockPart)) return false;
+
+ final MultiBlockPart part = (MultiBlockPart) tileEntity;
+ if (aRegistryID != part.getMultiTileEntityRegistryID() || aBlockMeta != part.getMultiTileEntityID()) return false;
+
+ final IMultiBlockController tTarget = part.getTarget(false);
+ if (tTarget != null && tTarget != MultiBlockController.this) return false;
+
+ part.setTarget(MultiBlockController.this, aModes);
+ return true;
+ }
+
+ @Override
+ public boolean spawnHint(S t, World world, int x, int y, int z, ItemStack trigger) {
+ if (mIcons == null) {
+ mIcons = new IIcon[6];
+ Arrays.fill(mIcons, TextureSet.SET_NONE.mTextures[OrePrefixes.block.mTextureIndex].getIcon());
+// Arrays.fill(mIcons, getTexture(aCasing);
+// for (int i = 0; i < 6; i++) {
+// mIcons[i] = aCasing.getIcon(i, aMeta);
+// }
+ }
+ final short[] RGBA = DEFAULT;
+ StructureLibAPI.hintParticleTinted(world, x, y, z, mIcons, RGBA);
+// StructureLibAPI.hintParticle(world, x, y, z, aCasing, aMeta);
+ return true;
+ }
+
+ @Override
+ public boolean placeBlock(S t, World world, int x, int y, int z, ItemStack trigger) {
+ final MultiTileEntityRegistry tRegistry = MultiTileEntityRegistry.getRegistry(aRegistryID);
+ final MultiTileEntityContainer tContainer = tRegistry.getNewTileEntityContainer(world, x, y, z, aBlockMeta, null);
+ if(tContainer == null) {
+ GT_FML_LOGGER.error("NULL CONTAINER");
+ return false;
+ }
+ final IMultiTileEntity te = ((IMultiTileEntity)tContainer.mTileEntity);
+ if(!(te instanceof MultiBlockPart)) {
+ GT_FML_LOGGER.error("Not a multiblock part");
+ return false;
+ }
+ if (world.setBlock(x, y, z, tContainer.mBlock, 15 - tContainer.mBlockMetaData, 2)) {
+ tContainer.setMultiTile(world, x, y, z);
+ ((MultiBlockPart) te).setTarget(MultiBlockController.this, aModes);
+ }
+
+ return false;
+ }
+
+ public IIcon getTexture(OrePrefixes aBlock) {
+ return TextureSet.SET_NONE.mTextures[OrePrefixes.block.mTextureIndex].getIcon();
+ }
+ };
+ }
+
+ /**
+ * Fluid - MultiBlock related Fluid Tank behaviour.
+ */
+
+ protected IFluidTank getFluidTankFillable(MultiBlockPart aPart, byte aSide, FluidStack aFluidToFill) {return getFluidTankFillable(aSide, aFluidToFill);}
+ protected IFluidTank getFluidTankDrainable(MultiBlockPart aPart, byte aSide, FluidStack aFluidToDrain) {return getFluidTankDrainable(aSide, aFluidToDrain);}
+ protected IFluidTank[] getFluidTanks(MultiBlockPart aPart, byte aSide) {return getFluidTanks(aSide);}
+
+ @Override
+ public int fill(MultiBlockPart aPart, ForgeDirection aDirection, FluidStack aFluid, boolean aDoFill) {
+ if (aFluid == null || aFluid.amount <= 0) return 0;
+ final IFluidTank tTank = getFluidTankFillable(aPart, (byte)aDirection.ordinal(), aFluid);
+ if (tTank == null) return 0;
+ final int rFilledAmount = tTank.fill(aFluid, aDoFill);
+ if (rFilledAmount > 0 && aDoFill) mInventoryChanged = true;
+ return rFilledAmount;
+ }
+
+ @Override
+ public FluidStack drain(MultiBlockPart aPart, ForgeDirection aDirection, FluidStack aFluid, boolean aDoDrain) {
+ if (aFluid == null || aFluid.amount <= 0) return null;
+ final IFluidTank tTank = getFluidTankDrainable(aPart, (byte)aDirection.ordinal(), aFluid);
+ if (tTank == null || tTank.getFluid() == null || tTank.getFluidAmount() == 0 || !tTank.getFluid().isFluidEqual(aFluid)) return null;
+ final FluidStack rDrained = tTank.drain(aFluid.amount, aDoDrain);
+ if (rDrained != null && aDoDrain) markInventoryBeenModified();
+ return rDrained;
+ }
+
+ @Override
+ public FluidStack drain(MultiBlockPart aPart, ForgeDirection aDirection, int aAmountToDrain, boolean aDoDrain) {
+ if (aAmountToDrain <= 0) return null;
+ final IFluidTank tTank = getFluidTankDrainable(aPart, (byte)aDirection.ordinal(), null);
+ if (tTank == null || tTank.getFluid() == null || tTank.getFluidAmount() == 0) return null;
+ final FluidStack rDrained = tTank.drain(aAmountToDrain, aDoDrain);
+ if (rDrained != null && aDoDrain) markInventoryBeenModified();
+ return rDrained;
+ }
+
+ @Override
+ public boolean canFill(MultiBlockPart aPart, ForgeDirection aDirection, Fluid aFluid) {
+ if (aFluid == null) return false;
+ final IFluidTank tTank = getFluidTankFillable(aPart, (byte)aDirection.ordinal(), new FluidStack(aFluid, 0));
+ return tTank != null && (tTank.getFluid() == null || tTank.getFluid().getFluid() == aFluid);
+ }
+
+ @Override
+ public boolean canDrain(MultiBlockPart aPart, ForgeDirection aDirection, Fluid aFluid) {
+ if (aFluid == null) return false;
+ final IFluidTank tTank = getFluidTankDrainable(aPart, (byte)aDirection.ordinal(), new FluidStack(aFluid, 0));
+ return tTank != null && (tTank.getFluid() != null && tTank.getFluid().getFluid() == aFluid);
+ }
+
+ @Override
+ public FluidTankInfo[] getTankInfo(MultiBlockPart aPart, ForgeDirection aDirection) {
+ final IFluidTank[] tTanks = getFluidTanks(aPart, (byte)aDirection.ordinal());
+ if (tTanks == null || tTanks.length <= 0) return GT_Values.emptyFluidTankInfo;
+ final FluidTankInfo[] rInfo = new FluidTankInfo[tTanks.length];
+ for (int i = 0; i < tTanks.length; i++) rInfo[i] = new FluidTankInfo(tTanks[i]);
+ return rInfo;
+ }
+
+ /**
+ * Energy - MultiBlock related Energy behavior
+ */
+
+ @Override
+ public boolean isUniversalEnergyStored(MultiBlockPart aPart, long aEnergyAmount) {
+ return getUniversalEnergyStored(aPart) >= aEnergyAmount;
+ }
+
+ @Override
+ public long getUniversalEnergyStored(MultiBlockPart aPart) {
+ return Math.min(getUniversalEnergyStored(), getUniversalEnergyCapacity());
+ }
+
+ @Override
+ public long getUniversalEnergyCapacity(MultiBlockPart aPart) {
+ return getUniversalEnergyCapacity();
+ }
+
+ @Override
+ public long getOutputAmperage(MultiBlockPart aPart) {
+ return getOutputAmperage();
+ }
+
+ @Override
+ public long getOutputVoltage(MultiBlockPart aPart) {
+ return getOutputVoltage();
+ }
+
+ @Override
+ public long getInputAmperage(MultiBlockPart aPart) {
+ return getInputAmperage();
+ }
+
+ @Override
+ public long getInputVoltage(MultiBlockPart aPart) {
+ return getInputVoltage();
+ }
+
+ @Override
+ public boolean decreaseStoredEnergyUnits(MultiBlockPart aPart, long aEnergy, boolean aIgnoreTooLittleEnergy) {
+ return decreaseStoredEnergyUnits(aEnergy, aIgnoreTooLittleEnergy);
+ }
+
+ @Override
+ public boolean increaseStoredEnergyUnits(MultiBlockPart aPart, long aEnergy, boolean aIgnoreTooMuchEnergy) {
+ return increaseStoredEnergyUnits(aEnergy, aIgnoreTooMuchEnergy);
+ }
+
+ @Override
+ public boolean drainEnergyUnits(MultiBlockPart aPart, byte aSide, long aVoltage, long aAmperage) {
+ return drainEnergyUnits(aSide, aVoltage, aAmperage);
+ }
+
+ @Override
+ public long injectEnergyUnits(MultiBlockPart aPart, byte aSide, long aVoltage, long aAmperage) {
+ return injectEnergyUnits(aSide, aVoltage, aAmperage);
+ }
+
+ @Override
+ public long getAverageElectricInput(MultiBlockPart aPart) {
+ return getAverageElectricInput();
+ }
+
+ @Override
+ public long getAverageElectricOutput(MultiBlockPart aPart) {
+ return getAverageElectricOutput();
+ }
+
+ @Override
+ public long getStoredEU(MultiBlockPart aPart) {
+ return getStoredEU();
+ }
+
+ @Override
+ public long getEUCapacity(MultiBlockPart aPart) {
+ return getEUCapacity();
+ }
+
+ @Override
+ public boolean inputEnergyFrom(MultiBlockPart aPart, byte aSide) {
+ if (aSide == GT_Values.SIDE_UNKNOWN) return true;
+ if (aSide >= 0 && aSide < 6) {
+ if(isInvalid()) return false;
+ if (isEnetInput()) return isEnergyInputSide(aSide);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean outputsEnergyTo(MultiBlockPart aPart, byte aSide) {
+ if (aSide == GT_Values.SIDE_UNKNOWN) return true;
+ if (aSide >= 0 && aSide < 6) {
+ if (isInvalid()) return false;
+ if (isEnetOutput()) return isEnergyOutputSide(aSide);
+ }
+ return false;
+ }
+
+ /**
+ * Item - MultiBlock related Item behaviour.
+ */
+
+
+ @Override
+ public boolean hasInventoryBeenModified(MultiBlockPart aPart) {
+ // TODO: MultiInventory - Figure this out based on locked & the part
+ return hasInventoryBeenModified();
+ }
+
+ @Override
+ public boolean isValidSlot(MultiBlockPart aPart, int aIndex) {
+ return false;
+ }
+
+ @Override
+ public boolean addStackToSlot(MultiBlockPart aPart, int aIndex, ItemStack aStack) {
+ return false;
+ }
+
+ @Override
+ public boolean addStackToSlot(MultiBlockPart aPart, int aIndex, ItemStack aStack, int aAmount) {
+ return false;
+ }
+
+ protected Pair<ItemStack[], Integer> getInventory(int lockedInventory, int aSlot) {
+ if (lockedInventory != -1) return new ImmutablePair<>(multiBlockInventory.get(lockedInventory), aSlot);
+
+ int start = 0;
+ for(ItemStack[] inv : multiBlockInventory) {
+ if (aSlot > start && aSlot < start + inv.length) {
+ return new ImmutablePair<>(inv, aSlot - start);
+ }
+ start += inv.length;
+ }
+ return null;
+ }
+
+ @Override
+ public int[] getAccessibleSlotsFromSide(MultiBlockPart aPart, byte aSide) {
+ final TIntList tList = new TIntArrayList();
+ final int lockedInventory = aPart.getLockedInventory();
+
+ int start = 0;
+ if(lockedInventory == -1) {
+ for (ItemStack[] inv : multiBlockInventory) {
+ for (int i = start; i < inv.length + start; i++) tList.add(i);
+ start += inv.length;
+ }
+ } else {
+ final int len = multiBlockInventory.get(lockedInventory).length;
+ for(int i = 0; i < len ; i++) tList.add(i);
+ }
+ return tList.toArray();
+ }
+
+
+ @Override
+ public boolean canInsertItem(MultiBlockPart aPart, int aSlot, ItemStack aStack, byte aSide) {
+ final int lockedInventory = aPart.getLockedInventory(), tSlot;
+ final ItemStack[] inv;
+ if(lockedInventory == -1) {
+ final Pair<ItemStack[], Integer> tInv = getInventory(lockedInventory, aSlot);
+ if(tInv == null) return false;
+ inv = tInv.getLeft();
+ tSlot = tInv.getRight();
+ } else {
+ inv = multiBlockInventory.get(lockedInventory);
+ tSlot = aSlot;
+ }
+ return inv[tSlot] == null || GT_Utility.areStacksEqual(aStack, inv[tSlot]); //&& allowPutStack(getBaseMetaTileEntity(), aIndex, (byte) aSide, aStack)
+ }
+
+ @Override
+ public boolean canExtractItem(MultiBlockPart aPart, int aSlot, ItemStack aStack, byte aSide) {
+ final int lockedInventory = aPart.getLockedInventory(), tSlot;
+ final ItemStack[] inv;
+ if(lockedInventory == -1) {
+ final Pair<ItemStack[], Integer> tInv = getInventory(lockedInventory, aSlot);
+ if(tInv == null) return false;
+ inv = tInv.getLeft();
+ tSlot = tInv.getRight();
+ } else {
+ inv = multiBlockInventory.get(lockedInventory);
+ tSlot = aSlot;
+ }
+ return inv[tSlot] != null; // && allowPullStack(getBaseMetaTileEntity(), aIndex, (byte) aSide, aStack);
+ }
+
+ @Override
+ public int getSizeInventory(MultiBlockPart aPart) {
+ final int lockedInventory = aPart.getLockedInventory();
+ if(lockedInventory == -1) {
+ int len = 0;
+ for (ItemStack[] inv : multiBlockInventory) len += inv.length;
+ return len;
+ } else {
+ return multiBlockInventory.get(lockedInventory).length;
+ }
+ }
+
+ @Override
+ public ItemStack getStackInSlot(MultiBlockPart aPart, int aSlot) {
+ final int lockedInventory = aPart.getLockedInventory(), tSlot;
+ final ItemStack[] inv;
+ if(lockedInventory == -1) {
+ final Pair<ItemStack[], Integer> tInv = getInventory(lockedInventory, aSlot);
+ if(tInv == null) return null;
+ inv = tInv.getLeft();
+ tSlot = tInv.getRight();
+ } else {
+ inv = multiBlockInventory.get(lockedInventory);
+ tSlot = aSlot;
+ }
+ return inv[tSlot];
+ }
+
+ @Override
+ public ItemStack decrStackSize(MultiBlockPart aPart, int aSlot, int aDecrement) {
+ final ItemStack tStack = getStackInSlot(aPart, aSlot);
+ ItemStack rStack = GT_Utility.copyOrNull(tStack);
+ if (tStack != null) {
+ if (tStack.stackSize <= aDecrement) {
+ setInventorySlotContents(aSlot, null);
+ } else {
+ rStack = tStack.splitStack(aDecrement);
+ if (tStack.stackSize == 0)
+ setInventorySlotContents(aSlot, null);
+ }
+ }
+ return rStack;
+ }
+
+ @Override
+ public ItemStack getStackInSlotOnClosing(MultiBlockPart aPart, int aSlot) {
+ final Pair<ItemStack[], Integer> tInv = getInventory(aPart.getLockedInventory(), aSlot);
+ if (tInv == null) return null;
+
+ final ItemStack[] inv = tInv.getLeft();
+ final int tSlot = tInv.getRight();
+
+ final ItemStack rStack = inv[tSlot];
+ inv[tSlot] = null;
+ return rStack;
+ }
+
+ @Override
+ public void setInventorySlotContents(MultiBlockPart aPart, int aSlot, ItemStack aStack) {
+ final Pair<ItemStack[], Integer> tInv = getInventory(aPart.getLockedInventory(), aSlot);
+ if (tInv == null) return;
+
+ final ItemStack[] inv = tInv.getLeft();
+ final int tSlot = tInv.getRight();
+ inv[tSlot] = aStack;
+ }
+
+ @Override
+ public String getInventoryName(MultiBlockPart aPart) {
+ return getInventoryName(); // TODO: MultiInventory: Include part Name?
+ }
+
+ @Override
+ public boolean hasCustomInventoryName(MultiBlockPart aPart) {
+ return hasCustomInventoryName();
+ }
+
+ @Override
+ public int getInventoryStackLimit(MultiBlockPart aPart) {
+ return getInventoryStackLimit();
+ }
+
+ @Override
+ public void markDirty(MultiBlockPart aPart) {
+ // TODO: MultiInventory - Consider the part?
+ markDirty(); markInventoryBeenModified();
+ }
+
+ @Override
+ public boolean isUseableByPlayer(MultiBlockPart aPart, EntityPlayer aPlayer) {
+ return isUseableByPlayer(aPlayer);
+ }
+
+ @Override
+ public void openInventory(MultiBlockPart aPart) {
+ // TODO: MultiInventory - consider the part's inventory
+ openInventory();
+ }
+
+ @Override
+ public void closeInventory(MultiBlockPart aPart) {
+ // TODO: MultiInventory - consider the part's inventory
+ closeInventory();
+ }
+
+ @Override
+ public boolean isItemValidForSlot(MultiBlockPart aPart, int aSlot, ItemStack aStack) {
+ return isItemValidForSlot(aSlot, aStack);
+ }
+}
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..9b5c76ef8f
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java
@@ -0,0 +1,600 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import gregtech.api.enums.GT_Values;
+import gregtech.api.interfaces.ITexture;
+import gregtech.api.multitileentity.base.BaseNontickableMultiTileEntity;
+import gregtech.api.multitileentity.interfaces.IMultiBlockController;
+import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_BreakBlock;
+import gregtech.api.render.TextureFactory;
+import gregtech.api.util.GT_CoverBehaviorBase;
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.ISerializableObject;
+import mcp.mobius.waila.api.IWailaConfigHandler;
+import mcp.mobius.waila.api.IWailaDataAccessor;
+import net.minecraft.block.Block;
+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.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.Fluid;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.FluidTankInfo;
+
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.math.LongMath.log2;
+import static gregtech.api.enums.GT_Values.ALL_VALID_SIDES;
+import static gregtech.api.enums.GT_Values.B;
+import static gregtech.api.enums.GT_Values.NBT;
+import static gregtech.api.enums.GT_Values.SIDE_UNKNOWN;
+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.MACHINE_CASINGS;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_ENERGY_IN_MULTI;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_ENERGY_OUT_MULTI;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_PIPE_IN;
+import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_PIPE_OUT;
+import static org.apache.commons.lang3.ObjectUtils.firstNonNull;
+
+public class MultiBlockPart extends BaseNontickableMultiTileEntity implements IMTE_BreakBlock {
+ 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 ChunkCoordinates mTargetPos = null;
+ protected IMultiBlockController mTarget = null;
+
+ protected int mAllowedModes = NOTHING; // BITMASK - Modes allowed for this part
+ protected byte mMode = 0; // Mode selected for this part
+
+ /**
+ * 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;
+ }
+
+ public int getLockedInventory() {
+ return -1;
+ }
+
+ public void setTarget(IMultiBlockController aTarget, int aAllowedModes) {
+ mTarget = aTarget;
+ mTargetPos = (mTarget == null ? null : mTarget.getCoords());
+ mAllowedModes = aAllowedModes;
+ }
+
+ @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(mMode));
+ }
+
+ @Override
+ public void getWailaBody(ItemStack itemStack, List<String> currenttip, IWailaDataAccessor accessor, IWailaConfigHandler config) {
+ super.getWailaBody(itemStack, currenttip, accessor, config);
+ currenttip.add(String.format("Mode: %s", getModeName(mMode)));
+ }
+
+ public IMultiBlockController getTarget(boolean aCheckValidity) {
+ if (mTargetPos == null) return null;
+ if (mTarget == null || mTarget.isDead()) {
+ if (worldObj.blockExists(mTargetPos.posX, mTargetPos.posY, mTargetPos.posZ)) {
+ final TileEntity te = worldObj.getTileEntity(mTargetPos.posX, mTargetPos.posY, mTargetPos.posZ);
+ if (te instanceof IMultiBlockController) {
+ mTarget = (IMultiBlockController)te;
+ } else {
+ mTargetPos = null;
+ }
+ }
+ }
+ if(aCheckValidity) {
+ return mTarget != null && mTarget.checkStructure(false) ? mTarget : null;
+ }
+ else
+ return mTarget;
+ }
+
+ @Override
+ public void readMultiTileNBT(NBTTagCompound aNBT) {
+ if (aNBT.hasKey(NBT.ALLOWED_MODES)) mAllowedModes = aNBT.getInteger(NBT.ALLOWED_MODES);
+ if (aNBT.hasKey(NBT.MODE)) mMode = aNBT.getByte(NBT.MODE);
+ if (aNBT.hasKey(NBT.TARGET)) {
+ mTargetPos = new ChunkCoordinates(aNBT.getInteger(NBT.TARGET_X), aNBT.getShort(NBT.TARGET_Y), aNBT.getInteger(NBT.TARGET_Z));
+ }
+
+ }
+
+ @Override
+ public void writeMultiTileNBT(NBTTagCompound aNBT) {
+ if (mAllowedModes != NOTHING) aNBT.setInteger(NBT.ALLOWED_MODES, mAllowedModes);
+ if (mMode != 0) aNBT.setInteger(NBT.MODE, mMode);
+ if (mTargetPos != null) {
+ aNBT.setBoolean(NBT.TARGET, true);
+ aNBT.setInteger(NBT.TARGET_X, mTargetPos.posX);
+ aNBT.setShort(NBT.TARGET_Y, (short)mTargetPos.posY);
+ aNBT.setInteger(NBT.TARGET_Z, mTargetPos.posZ);
+ }
+ }
+
+ /**
+ * True if `aMode` is one of the allowed modes
+ */
+ public boolean hasMode(int aMode) {
+ return (mAllowedModes & 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) && mMode == getModeOrdinal(aMode))
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean breakBlock() {
+ final IMultiBlockController tTarget = getTarget(false);
+ if (tTarget != null) tTarget.onStructureChange();
+ return false;
+ }
+
+ @Override
+ public void onBlockAdded() {
+ for (byte tSide : ALL_VALID_SIDES) {
+ final TileEntity te = getTileEntityAtSide(tSide);
+ if (te instanceof MultiBlockPart) {
+ final IMultiBlockController tController = ((MultiBlockPart)te).getTarget(false);
+ if (tController != null) tController.onStructureChange();
+ } else if (te instanceof IMultiBlockController) {
+ ((IMultiBlockController)te).onStructureChange();
+ }
+ }
+ }
+
+ @Override
+ public byte getTextureData() {
+ return mMode;
+ }
+
+ @Override
+ public void setTextureData(byte aData) {
+ mMode = aData;
+ }
+
+ @Override
+ public ITexture[] getTexture(Block aBlock, byte aSide, boolean isActive, int aRenderPass) {
+ final ITexture baseTexture = MACHINE_CASINGS[1][2];
+ if(mMode != 0 && aSide == mFacing) {
+ if(mMode == getModeOrdinal(ITEM_IN))
+ return new ITexture[]{ baseTexture, TextureFactory.of(OVERLAY_PIPE_IN), TextureFactory.of(ITEM_IN_SIGN) };
+ if(mMode == getModeOrdinal(ITEM_OUT))
+ return new ITexture[]{ baseTexture, TextureFactory.of(OVERLAY_PIPE_OUT), TextureFactory.of(ITEM_OUT_SIGN) };
+ if(mMode == getModeOrdinal(FLUID_IN))
+ return new ITexture[]{ baseTexture, TextureFactory.of(OVERLAY_PIPE_IN), TextureFactory.of(FLUID_IN_SIGN) };
+ if(mMode == getModeOrdinal(FLUID_OUT))
+ return new ITexture[]{ baseTexture, TextureFactory.of(OVERLAY_PIPE_OUT), TextureFactory.of(FLUID_OUT_SIGN) };
+ if(mMode == getModeOrdinal(ENERGY_IN))
+ return new ITexture[]{ baseTexture, TextureFactory.of(OVERLAY_ENERGY_IN_MULTI)};
+ if(mMode == getModeOrdinal(ENERGY_OUT))
+ return new ITexture[]{ baseTexture, TextureFactory.of(OVERLAY_ENERGY_OUT_MULTI)};
+
+ }
+ return new ITexture[]{ baseTexture };
+ }
+
+ @Override
+ public boolean isUseableByPlayer(EntityPlayer entityPlayer) {
+ return false;
+ }
+
+ protected String getModeName(int aMode) {
+ if(aMode == NOTHING)
+ return "Nothing";
+ if(aMode == getModeOrdinal(ITEM_IN))
+ return "Item Input";
+ 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(mAllowedModes == NOTHING)
+ return NOTHING;
+
+ final int numModes = allowedModes.size();
+ for(byte i = 1 ; i <= numModes ; i++) {
+ final byte curMode = (byte)((mMode + i) % numModes);
+ if(curMode == NOTHING || hasMode(1 << (curMode - 1)))
+ return curMode;
+ }
+ // Nothing valid found
+ return 0;
+ }
+
+ @Override
+ public boolean onMalletRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, byte wrenchSide, float aX, float aY, float aZ) {
+ if(mAllowedModes == NOTHING)
+ return true;
+
+ mMode = getNextAllowedMode(BASIC_MODES);
+ GT_Utility.sendChatToPlayer(aPlayer, "Mode set to `" + getModeName(mMode) + "' (" + mMode + ")");
+ sendClientData((EntityPlayerMP) aPlayer);
+ return true;
+ }
+
+ @Override
+ public void setLightValue(byte aLightValue) {
+
+ }
+
+ @Override
+ public byte getComparatorValue(byte aSide) {
+ return 0;
+ }
+
+
+ @Override public String getTileEntityName() {
+ return "gt.multitileentity.multiblock.part";
+ }
+
+ /**
+ * TODO: Make sure the energy/item/fluid hatch is facing that way! or has that mode enabled on that side
+ * Check SIDE_UNKNOWN for or coverbehavior
+ */
+
+ /**
+ * Fluid - Depending on the part type - proxy it to the multiblock controller, if we have one
+ */
+ @Override
+ public int fill(ForgeDirection aDirection, FluidStack aFluidStack, boolean aDoFill) {
+ if (!modeSelected(FLUID_IN)) return 0;
+ final byte aSide = (byte)aDirection.ordinal();
+ if(aDirection != ForgeDirection.UNKNOWN && (aSide != mFacing || !coverLetsFluidIn(aSide, aFluidStack == null ? null : aFluidStack.getFluid())))
+ return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller == null ? 0 : controller.fill(this, aDirection, aFluidStack, aDoFill);
+ }
+
+ @Override
+ public FluidStack drain(ForgeDirection aDirection, FluidStack aFluidStack, boolean aDoDrain) {
+ if (!modeSelected(FLUID_OUT)) return null;
+ final byte aSide = (byte)aDirection.ordinal();
+ if(aDirection != ForgeDirection.UNKNOWN && (aSide != mFacing || !coverLetsFluidOut(aSide, aFluidStack == null ? null : aFluidStack.getFluid())))
+ return null;
+ final IMultiBlockController controller = getTarget(true);
+ return controller == null ? null : controller.drain(this, aDirection, aFluidStack, aDoDrain);
+ }
+
+ @Override
+ public FluidStack drain(ForgeDirection aDirection, int aAmountToDrain, boolean aDoDrain) {
+ if (!modeSelected(FLUID_OUT)) return null;
+ final byte aSide = (byte)aDirection.ordinal();
+ final IMultiBlockController controller = getTarget(true);
+ if (controller == null) return null;
+ final FluidStack aFluidStack = controller.getDrainableFluid(aSide);
+ if(aDirection != ForgeDirection.UNKNOWN && (aSide != mFacing || !coverLetsFluidOut(aSide, aFluidStack == null ? null : aFluidStack.getFluid())))
+ return null;
+ return controller.drain(this, aDirection, aAmountToDrain, aDoDrain);
+ }
+
+ @Override
+ public boolean canFill(ForgeDirection aDirection, Fluid aFluid) {
+ if (!modeSelected(FLUID_IN)) return false;
+ final byte aSide = (byte)aDirection.ordinal();
+ if(aDirection != ForgeDirection.UNKNOWN && (aSide != mFacing || !coverLetsFluidIn(aSide, aFluid)))
+ return false;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null && controller.canFill(this, aDirection, aFluid);
+ }
+
+ @Override
+ public boolean canDrain(ForgeDirection aDirection, Fluid aFluid) {
+ if (!modeSelected(FLUID_OUT)) return false;
+ final byte aSide = (byte)aDirection.ordinal();
+ if(aDirection != ForgeDirection.UNKNOWN && (aSide != mFacing || !coverLetsFluidOut(aSide, aFluid)))
+ return false;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null && controller.canDrain(this, aDirection, aFluid);
+ }
+
+ @Override
+ public FluidTankInfo[] getTankInfo(ForgeDirection aDirection) {
+ final byte aSide = (byte) aDirection.ordinal();
+ if (!modeSelected(FLUID_IN, FLUID_OUT) || (aSide != SIDE_UNKNOWN && aSide != mFacing)) return GT_Values.emptyFluidTankInfo;
+ final IMultiBlockController controller = getTarget(true);
+ if(controller == null) return GT_Values.emptyFluidTankInfo;
+
+ final GT_CoverBehaviorBase<?> tCover = getCoverBehaviorAtSideNew(aSide);
+ final int coverId = getCoverIDAtSide(aSide);
+ final ISerializableObject complexCoverData = getComplexCoverDataAtSide(aSide);
+
+ if((controller.isLiquidInput(aSide) && tCover.letsFluidIn(aSide, coverId, complexCoverData, null, controller)) ||
+ (controller.isLiquidOutput(aSide) && tCover.letsFluidOut(aSide, coverId, complexCoverData, null, controller)))
+ return controller.getTankInfo(this, aDirection);
+
+ return GT_Values.emptyFluidTankInfo;
+ }
+
+ /**
+ * Energy - Depending on the part type - proxy to the multiblock controller, if we have one
+ */
+
+ @Override
+ public boolean isEnetInput() {
+ return modeSelected(ENERGY_IN);
+ }
+
+ @Override
+ public boolean isEnetOutput() {
+ return modeSelected(ENERGY_OUT);
+ }
+
+ @Override
+ public boolean isUniversalEnergyStored(long aEnergyAmount) {
+ if (!modeSelected(ENERGY_OUT, ENERGY_IN)) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null && controller.isUniversalEnergyStored(this, aEnergyAmount);
+
+ }
+
+ @Override
+ public long getUniversalEnergyStored() {
+ if (!modeSelected(ENERGY_OUT, ENERGY_IN)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getUniversalEnergyStored(this) : 0;
+ }
+
+ @Override
+ public long getUniversalEnergyCapacity() {
+ if (!modeSelected(ENERGY_OUT, ENERGY_IN)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getUniversalEnergyCapacity(this) : 0;
+ }
+
+ @Override
+ public long getOutputAmperage() {
+ if (!modeSelected(ENERGY_OUT)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getOutputAmperage(this) : 0;
+ }
+
+ @Override
+ public long getOutputVoltage() {
+ if (!modeSelected(ENERGY_OUT)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getOutputVoltage(this) : 0;
+ }
+
+ @Override
+ public long getInputAmperage() {
+ if (!modeSelected(ENERGY_IN)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return (controller != null && hasMode(ENERGY_IN)) ? controller.getInputAmperage(this) : 0;
+ }
+
+ @Override
+ public long getInputVoltage() {
+ if (!modeSelected(ENERGY_IN)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return (controller != null && hasMode(ENERGY_IN)) ? controller.getInputVoltage(this) : 0;
+ }
+
+ @Override
+ public boolean decreaseStoredEnergyUnits(long aEnergy, boolean aIgnoreTooLittleEnergy) {
+ if (!modeSelected(ENERGY_IN)) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null && hasMode(ENERGY_OUT) && controller.decreaseStoredEnergyUnits(this, aEnergy, aIgnoreTooLittleEnergy);
+ }
+
+ @Override
+ public boolean increaseStoredEnergyUnits(long aEnergy, boolean aIgnoreTooMuchEnergy) {
+ if (!modeSelected(ENERGY_IN)) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null && hasMode(ENERGY_IN) && controller.increaseStoredEnergyUnits(this, aEnergy, aIgnoreTooMuchEnergy);
+ }
+
+ @Override
+ public boolean drainEnergyUnits(byte aSide, long aVoltage, long aAmperage) {
+ if(!modeSelected(ENERGY_OUT) || (mFacing != SIDE_UNKNOWN && (mFacing != aSide || !coverLetsEnergyOut(aSide)))) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null && controller.drainEnergyUnits(this, aSide, aVoltage, aAmperage);
+ }
+
+
+ @Override
+ public long injectEnergyUnits(byte aSide, long aVoltage, long aAmperage) {
+ if (!modeSelected(ENERGY_IN) || (mFacing != SIDE_UNKNOWN && (mFacing != aSide || !coverLetsEnergyIn(aSide)))) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.injectEnergyUnits(this, aSide, aVoltage, aAmperage) : 0;
+ }
+
+
+ @Override
+ public long getAverageElectricInput() {
+ if (!modeSelected(ENERGY_IN)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null? controller.getAverageElectricInput(this) : 0;
+ }
+
+ @Override
+ public long getAverageElectricOutput() {
+ if (!modeSelected(ENERGY_OUT)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getAverageElectricOutput(this) : 0;
+ }
+
+ @Override
+ public long getStoredEU() {
+ if (!modeSelected(ENERGY_OUT, ENERGY_IN)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getStoredEU(this) : 0;
+ }
+
+ @Override
+ public long getEUCapacity() {
+ if (!modeSelected(ENERGY_OUT, ENERGY_IN)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getEUCapacity(this) : 0;
+ }
+
+ @Override
+ public boolean inputEnergyFrom(byte aSide) {
+ if (!modeSelected(ENERGY_IN) || (mFacing != SIDE_UNKNOWN && (mFacing != aSide || !coverLetsEnergyIn(aSide)))) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null && controller.inputEnergyFrom(this, aSide);
+ }
+ @Override
+ public boolean outputsEnergyTo(byte aSide) {
+ if(!modeSelected(ENERGY_OUT) || (mFacing != SIDE_UNKNOWN && (mFacing != aSide || !coverLetsEnergyOut(aSide)))) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null && controller.outputsEnergyTo(this, aSide);
+ }
+
+ // End Energy
+
+ /**
+ * Inventory - Depending on the part type - proxy to the multiblock controller, if we have one
+ */
+
+ @Override
+ public boolean hasInventoryBeenModified() {
+ final IMultiBlockController controller = getTarget(true);
+ return (controller != null && controller.hasInventoryBeenModified(this));
+ }
+
+ @Override
+ public boolean isValidSlot(int aIndex) {
+ final IMultiBlockController controller = getTarget(true);
+ return (controller != null && controller.isValidSlot(this, aIndex));
+ }
+
+ @Override
+ public boolean addStackToSlot(int aIndex, ItemStack aStack) {
+ if (!modeSelected(ITEM_IN)) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return (controller != null && controller.addStackToSlot(this, aIndex, aStack));
+ }
+
+ @Override
+ public boolean addStackToSlot(int aIndex, ItemStack aStack, int aAmount) {
+ if (!modeSelected(ITEM_IN)) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return (controller != null && controller.addStackToSlot(this, aIndex, aStack, aAmount));
+ }
+
+
+ @Override
+ public int[] getAccessibleSlotsFromSide(int aSide) {
+ if (!modeSelected(ITEM_IN, ITEM_OUT) || (mFacing != SIDE_UNKNOWN && mFacing != aSide)) return GT_Values.emptyIntArray;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getAccessibleSlotsFromSide(this, (byte) aSide) : GT_Values.emptyIntArray;
+ }
+
+ @Override
+ public boolean canInsertItem(int aSlot, ItemStack aStack, int aSide) {
+ if (!modeSelected(ITEM_IN) || (mFacing != SIDE_UNKNOWN && (mFacing != aSide || !coverLetsItemsIn((byte)aSide, aSlot)))) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return (controller != null && controller.canInsertItem(this, aSlot, aStack, (byte) aSide));
+ }
+ @Override
+ public boolean canExtractItem(int aSlot, ItemStack aStack, int aSide) {
+ if (!modeSelected(ITEM_OUT) || (mFacing != SIDE_UNKNOWN && (mFacing != aSide || !coverLetsItemsOut((byte)aSide, aSlot)))) return false;
+ final IMultiBlockController controller = getTarget(true);
+ return (controller != null && controller.canExtractItem(this, aSlot, aStack, (byte) aSide));
+ }
+
+ @Override
+ public int getSizeInventory() {
+ if (!modeSelected(ITEM_IN, ITEM_OUT)) return 0;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getSizeInventory(this) : 0;
+ }
+
+ @Override
+ public ItemStack getStackInSlot(int aSlot) {
+ if (!modeSelected(ITEM_IN, ITEM_OUT)) return null;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getStackInSlot(this, aSlot) : null;
+ }
+
+ @Override
+ public ItemStack decrStackSize(int aSlot, int aDecrement) {
+ if (!modeSelected(ITEM_OUT)) return null;
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.decrStackSize(this, aSlot, aDecrement) : null;
+ }
+
+ @Override
+ public ItemStack getStackInSlotOnClosing(int aSlot) {
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getStackInSlotOnClosing(this, aSlot) : null;
+ }
+
+ @Override
+ public void setInventorySlotContents(int aSlot, ItemStack aStack) {
+ final IMultiBlockController controller = getTarget(true);
+ if(controller != null) controller.setInventorySlotContents(this, aSlot, aStack);
+ }
+
+ @Override
+ public String getInventoryName() {
+ final IMultiBlockController controller = getTarget(true);
+ if(controller != null)
+ return controller.getInventoryName(this);
+ return firstNonNull(getCustomName(), getTileEntityName());
+ }
+
+ @Override
+ public int getInventoryStackLimit() {
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null ? controller.getInventoryStackLimit(this) : 0;
+ }
+
+
+ @Override
+ public boolean isItemValidForSlot(int aSlot, ItemStack aStack) {
+ final IMultiBlockController controller = getTarget(true);
+ return controller != null && controller.isItemValidForSlot(this, aSlot, aStack);
+ }
+
+ // End Inventory
+
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlock_Stackable.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlock_Stackable.java
new file mode 100644
index 0000000000..f19d3156fa
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlock_Stackable.java
@@ -0,0 +1,114 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import com.gtnewhorizon.structurelib.util.Vec3Impl;
+
+import net.minecraft.item.ItemStack;
+
+public abstract class MultiBlock_Stackable<T extends MultiBlock_Stackable<T>> extends MultiBlockController<T> {
+ protected static String STACKABLE_TOP = "STACKABLE_TOP";
+ protected static String STACKABLE_MIDDLE = "STACKABLE_MIDDLE";
+ protected static String STACKABLE_BOTTOM = "STACKABLE_BOTTOM";
+
+ /**
+ * 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(STACKABLE_BOTTOM, trigger, hintsOnly, buildState.getCurrentOffset());
+ buildState.addOffset(getStartingStackOffset());
+
+ for (int i = 0; i < stackCount; i++) {
+ buildPiece(STACKABLE_MIDDLE, trigger, hintsOnly, buildState.getCurrentOffset());
+ buildState.addOffset(getPerStackOffset());
+ }
+ if(hasTop()) {
+ buildState.addOffset(getAfterLastStackOffset());
+ buildPiece(STACKABLE_TOP, 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() {
+ int stackCount = 0;
+
+ buildState.startBuilding(getStartingStructureOffset());
+ if (!checkPiece(STACKABLE_BOTTOM, buildState.getCurrentOffset()))
+ return buildState.failBuilding();
+
+ buildState.addOffset(getStartingStackOffset());
+
+ for (int i = 0; i < getMaxStacks(); i++) {
+ if (checkPiece(STACKABLE_MIDDLE, buildState.getCurrentOffset())) {
+ buildState.addOffset(getPerStackOffset());
+ stackCount++;
+ } else {
+ break;
+ }
+ }
+ if (stackCount < getMinStacks())
+ return buildState.failBuilding();
+
+ buildState.addOffset(getAfterLastStackOffset());
+ return checkPiece(STACKABLE_TOP, buildState.stopBuilding());
+ }
+
+ @Override
+ public boolean checkRecipe(ItemStack aStack) {
+ return false;
+ }
+
+}
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..8c507db88c
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/casing/CasingBehaviorBase.java
@@ -0,0 +1,17 @@
+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 {
+}