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