diff options
Diffstat (limited to 'src/main/java/gregtech/api/multitileentity')
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 { +} |