diff options
Diffstat (limited to 'src/main/java/gregtech/api/multitileentity')
35 files changed, 6908 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..f5cf261be1 --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityBlock.java @@ -0,0 +1,592 @@ +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.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockContainer; +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.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.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 BlockContainer implements IDebugableBlock, ITileEntityProvider, IFacade { + + private MultiTileEntityRegistry registry; + + private static boolean LOCK = false; + + private boolean registered = false; + private boolean normalCube; + protected String internalName, toolName, materialName, modID; + + public String getName() { + return String.join( + ".", + "gt.block.multiblock", + materialName, + stepSound.soundName, + toolName, + Boolean.toString(opaque), + Boolean.toString(normalCube)); + } + + public MultiTileEntityBlock(Material material) { + super(material); + if (GregTech_API.sPreloadFinished) + throw new IllegalStateException("Blocks can only be initialized within preInit!"); + } + + public MultiTileEntityBlock tool(String toolName) { + this.toolName = toolName.toLowerCase(); + return this; + } + + public MultiTileEntityBlock sound(SoundType soundtype) { + setStepSound(soundtype); + return this; + } + + public MultiTileEntityBlock opaque(Boolean opaque) { + this.opaque = opaque; + return this; + } + + public MultiTileEntityBlock normalCube(Boolean normalCube) { + this.normalCube = normalCube; + return this; + } + + public MultiTileEntityBlock modID(String modID) { + this.modID = modID; + return this; + } + + public MultiTileEntityBlock materialName(String materialName) { + this.materialName = materialName; + return this; + } + + public MultiTileEntityBlock register() { + if (registered) throw new IllegalStateException("Block already registered " + internalName); + if (GregTech_API.sPreloadFinished) + throw new IllegalStateException("Blocks can only be initialized within preInit!"); + + registered = true; + internalName = getName(); + lightOpacity = isOpaqueCube() ? 255 : 0; + + GameRegistry.registerBlock(this, MultiTileEntityItem.class, internalName); + return this; + } + + @Override + public final void breakBlock(World world, int x, int y, int z, Block block, int metadata) { + final TileEntity tileEntity = getTileEntity(world, x, y, z, true); + if (tileEntity != null) LAST_BROKEN_TILEENTITY.set(tileEntity); + if (tileEntity == null || !tileEntity.shouldRefresh(this, block, metadata, metadata, world, x, y, z)) return; + if (tileEntity instanceof IMultiTileEntity mute) mute.onBlockBroken(); + // spotless:off + // if (aTileEntity instanceof IMTE_HasMultiBlockMachineRelevantData + // && ((IMTE_HasMultiBlockMachineRelevantData) aTileEntity).hasMultiBlockMachineRelevantData()) + // GregTech_API.causeMachineUpdate(world, x, y, z); + // spotless:on + + world.removeTileEntity(x, y, z); + super.breakBlock(world, x, y, z, block, metadata); + + } + + @Override + public ArrayList<String> getDebugInfo(EntityPlayer aPlayer, int x, int y, int z, int aLogLevel) { + final TileEntity tileEntity = aPlayer.worldObj.getTileEntity(x, y, z); + if (tileEntity instanceof IDebugableTileEntity mte) { + return mte.getDebugInfo(aPlayer, aLogLevel); + } + return new ArrayList<>(); + } + + @Override + public int getRenderType() { + return GT_MultiTile_Renderer.INSTANCE == null ? super.getRenderType() + : GT_MultiTile_Renderer.INSTANCE.getRenderId(); + } + + @Override + public final float getBlockHardness(World world, int x, int y, int z) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity ? ((IMultiTileEntity) tileEntity).getBlockHardness() : 1.0F; + } + + @SideOnly(Side.CLIENT) + @Override + public IIcon getIcon(IBlockAccess aIBlockAccess, int x, int y, int z, int ordinalSide) { + return Textures.BlockIcons.MACHINE_LV_SIDE.getIcon(); + } + + @SideOnly(Side.CLIENT) + @Override + public IIcon getIcon(int ordinalSide, int meta) { + return Textures.BlockIcons.MACHINE_LV_SIDE.getIcon(); + } + + @Override + @SuppressWarnings("unchecked") + public final void addCollisionBoxesToList(World world, int x, int y, int z, AxisAlignedBB aAABB, + List<AxisAlignedBB> aList, Entity aEntity) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + if (tileEntity instanceof IMultiTileEntity) + ((IMultiTileEntity) tileEntity).addCollisionBoxesToList(aAABB, aList, aEntity); + else super.addCollisionBoxesToList(world, x, y, z, aAABB, aList, aEntity); + } + + @Override + public final AxisAlignedBB getCollisionBoundingBoxFromPool(World world, int x, int y, int z) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity mte ? mte.getCollisionBoundingBoxFromPool() + : tileEntity == null ? null : super.getCollisionBoundingBoxFromPool(world, x, y, z); + } + + @Override + public final AxisAlignedBB getSelectedBoundingBoxFromPool(World world, int x, int y, int z) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity mte ? mte.getSelectedBoundingBoxFromPool() + : super.getSelectedBoundingBoxFromPool(world, x, y, z); + } + + @Override + public void setBlockBoundsBasedOnState(IBlockAccess blockAccess, int x, int y, int z) { + final TileEntity tileEntity = blockAccess.getTileEntity(x, y, z); + if (tileEntity instanceof IMultiTileEntity mte) { + mte.setBlockBoundsBasedOnState(this); + return; + } + super.setBlockBoundsBasedOnState(blockAccess, x, y, z); + } + + @Override + public final boolean isOpaqueCube() { + return opaque; + } + + @Override + public final void onNeighborChange(IBlockAccess world, int x, int y, int z, int aTileX, int aTileY, int aTileZ) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + if (!LOCK) { + LOCK = true; + if (tileEntity instanceof BaseTileEntity) + ((BaseTileEntity) tileEntity).onAdjacentBlockChange(aTileX, aTileY, aTileZ); + LOCK = false; + } + } + + @Override + public void onNeighborBlockChange(World world, int x, int y, int z, Block block) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + if (!LOCK) { + LOCK = true; + if (tileEntity instanceof BaseTileEntity bte) bte.onAdjacentBlockChange(x, y, z); + LOCK = false; + } + if (tileEntity instanceof IMultiTileEntity change) change.onNeighborBlockChange(world, block); + + if (tileEntity == null) world.setBlockToAir(x, y, z); + } + + @Override + public final void onBlockAdded(World world, int x, int y, int z) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + if (tileEntity instanceof IMultiTileEntity mte) mte.onBlockAdded(); + } + + @Override + public float getPlayerRelativeBlockHardness(EntityPlayer aPlayer, World world, int x, int y, int z) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity mte && mte.privateAccess() + && !((IMultiTileEntity) tileEntity).playerOwnsThis(aPlayer, true) ? -1.0F + : super.getPlayerRelativeBlockHardness(aPlayer, world, x, y, z); + } + + @Override + public final void onBlockClicked(World world, int x, int y, int z, EntityPlayer aPlayer) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + if (tileEntity instanceof IMultiTileEntity mte) mte.onLeftClick(aPlayer); + else super.onBlockClicked(world, x, y, z, aPlayer); + } + + @Override + public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer aPlayer, int ordinalSide, + float aHitX, float aHitY, float aHitZ) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + if (aPlayer != null && ItemList.TC_Thaumometer.isStackEqual(aPlayer.getHeldItem(), true, true)) return false; + return tileEntity instanceof IMultiTileEntity mte + && mte.onBlockActivated(aPlayer, ForgeDirection.getOrientation(ordinalSide), aHitX, aHitY, aHitZ); + } + + @Override + public final int isProvidingWeakPower(IBlockAccess world, int x, int y, int z, int ordinalSide) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity power + ? power.isProvidingWeakPower(ForgeDirection.getOrientation(ordinalSide)) + : super.isProvidingWeakPower(world, x, y, z, ordinalSide); + } + + @Override + public final int isProvidingStrongPower(IBlockAccess world, int x, int y, int z, int ordinalSide) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity power + ? power.isProvidingStrongPower(ForgeDirection.getOrientation(ordinalSide)) + : super.isProvidingStrongPower(world, x, y, z, ordinalSide); + } + + @Override + public final boolean shouldCheckWeakPower(IBlockAccess world, int x, int y, int z, int ordinalSide) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity power + ? power.shouldCheckWeakPower(ForgeDirection.getOrientation(ordinalSide)) + : isNormalCube(world, x, y, z); + } + + @Override + public final boolean getWeakChanges(IBlockAccess world, int x, int y, int z) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity changes ? changes.getWeakChanges() + : super.getWeakChanges(world, x, y, z); + } + + @Override + public final void harvestBlock(World world, EntityPlayer aPlayer, int x, int y, int z, int meta) { + 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 tileEntity = getTileEntity(world, x, y, z, true); + + if (!(tileEntity instanceof IMultiTileEntity mte)) { + return; + } + + final ArrayList<ItemStack> tList = mte.getDrops(aFortune, aSilkTouch); + aChance = ForgeEventFactory + .fireBlockHarvesting(tList, world, this, x, y, z, meta, aFortune, aChance, aSilkTouch, aPlayer); + for (final ItemStack tStack : tList) + if (XSTR.XSTR_INSTANCE.nextFloat() <= aChance) dropBlockAsItem(world, x, y, z, tStack); + + } + + @Override + public final boolean shouldSideBeRendered(IBlockAccess world, int x, int y, int z, int ordinalSide) { + final TileEntity tileEntity = world + .getTileEntity(x - OFFX[ordinalSide], y - OFFY[ordinalSide], z - OFFZ[ordinalSide]); + return tileEntity instanceof IMultiTileEntity mte + ? mte.shouldSideBeRendered(ForgeDirection.getOrientation(ordinalSide)) + : super.shouldSideBeRendered(world, x, y, z, ordinalSide); + } + + @Override + public Block getFacade(IBlockAccess world, int x, int y, int z, int ordinalSide) { + final TileEntity tTileEntity = world.getTileEntity(x, y, z); + 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 world, int x, int y, int z, int ordinalSide) { + final TileEntity tTileEntity = world.getTileEntity(x, y, z); + 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; + } + + public MultiTileEntityRegistry getRegistry() { + return registry; + } + + public void setRegistry(MultiTileEntityRegistry registry) { + this.registry = registry; + } + + public boolean isRegistered() { + return registered; + } + + @Override + protected boolean canSilkHarvest() { + return false; + } + + @Override + public final String getLocalizedName() { + return StatCollector.translateToLocal(internalName + ".name"); + } + + @Override + public final String getUnlocalizedName() { + return internalName; + } + + @Override + public final boolean onBlockEventReceived(World world, int x, int y, int z, int aID, int aData) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity == null || tileEntity.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 world, int x, int y, int z, int ordinalSide) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + if (tileEntity instanceof IMultiTileEntity mute) { + if (mute.hasComparatorInputOverride()) + return mute.getComparatorInputOverride(ForgeDirection.getOrientation(ordinalSide)); + else return mute.isProvidingWeakPower( + ForgeDirection.getOrientation(ordinalSide) + .getOpposite()); + } + + return super.getComparatorInputOverride(world, x, y, z, ordinalSide); + } + + @Override + public final void registerBlockIcons(IIconRegister aIconRegister) { + /* Do Nothing */ + } + + @Override + public final boolean isSideSolid(IBlockAccess world, int x, int y, int z, ForgeDirection side) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity mute ? mute.isSideSolid(side) : opaque; + } + + @Override + public boolean removedByPlayer(World world, EntityPlayer aPlayer, int x, int y, int z, boolean aWillHarvest) { + final TileEntity tileEntity = GT_Util.getTileEntity(world, x, y, z, true); + if (tileEntity != null) LAST_BROKEN_TILEENTITY.set(tileEntity); + return super.removedByPlayer(world, aPlayer, x, y, z, aWillHarvest); + } + + @Override + public int getFlammability(IBlockAccess world, int x, int y, int z, ForgeDirection face) { + return 0; + } + + @Override + public int getFireSpreadSpeed(IBlockAccess world, int x, int y, int z, ForgeDirection face) { + return GregTech_API.sMachineFlammable && (world.getBlockMetadata(x, y, z) == 0) ? 100 : 0; + } + + @Override + public boolean hasTileEntity(int meta) { + return true; + } + + @Override + public final ArrayList<ItemStack> getDrops(World world, int x, int y, int z, int aUnusableMetaData, int aFortune) { + final TileEntity tileEntity = getTileEntity(world, x, y, z, true); + if (tileEntity instanceof IMultiTileEntity mute) return mute.getDrops(aFortune, false); + return new ArrayList<>(); + } + + @Override + public boolean canCreatureSpawn(EnumCreatureType type, IBlockAccess world, int x, int y, int z) { + return false; + } + + @Override + public final float getExplosionResistance(Entity aExploder, World world, int x, int y, int z, double aExplosionX, + double aExplosionY, double aExplosionZ) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity mute + ? mute.getExplosionResistance(aExploder, aExplosionX, aExplosionY, aExplosionZ) + : 1.0F; + } + + @Override + public final void onBlockExploded(World world, int x, int y, int z, Explosion aExplosion) { + if (world.isRemote) return; + final TileEntity tileEntity = getTileEntity(world, x, y, z, true); + if (tileEntity != null) LAST_BROKEN_TILEENTITY.set(tileEntity); + if (tileEntity instanceof IMultiTileEntity mute) { + GT_Log.exp.printf( + "Explosion at : %d | %d | %d DIMID: %s due to near explosion!%n", + x, + y, + z, + world.provider.dimensionId); + mute.onExploded(aExplosion); + } else world.setBlockToAir(x, y, z); + } + + @Override + public final boolean canConnectRedstone(IBlockAccess world, int x, int y, int z, int ordinalSide) { + return true; + } + + @Override + public final boolean recolourBlock(World world, int x, int y, int z, ForgeDirection side, int color) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity mute && mute.recolourBlock(side, (byte) color); + } + + @Override + public final String getHarvestTool(int meta) { + return toolName; + } + + @Override + public int getHarvestLevel(int meta) { + return meta % 4; + } + + @Override + public final boolean isToolEffective(String toolType, int meta) { + return getHarvestTool(meta).equals(toolType); + } + + @Override + public final ItemStack getPickBlock(MovingObjectPosition target, World world, int x, int y, int z, + EntityPlayer aPlayer) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity mte ? mte.getPickBlock(target) : null; + } + + @Override + public final ItemStack getPickBlock(MovingObjectPosition target, World world, int x, int y, int z) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + return tileEntity instanceof IMultiTileEntity mte ? mte.getPickBlock(target) : null; + } + + @Nullable + public final IMultiTileEntity receiveMultiTileEntityData(@Nonnull IBlockAccess world, int x, int y, int z, + int registryId, int aID) { + if (!(world instanceof World)) return null; + TileEntity tileEntity = world.getTileEntity(x, y, z); + + if (!(tileEntity instanceof IMultiTileEntity mute) || mute.getMultiTileEntityRegistryID() != registryId + || mute.getMultiTileEntityID() != aID) { + final MultiTileEntityRegistry tRegistry = MultiTileEntityRegistry.getRegistry(registryId); + if (tRegistry == null) return null; + + tileEntity = tRegistry.getNewTileEntity((World) world, x, y, z, aID); + if (!(tileEntity instanceof IMultiTileEntity)) return null; + + setTileEntity((World) world, x, y, z, tileEntity, false); + } + return (IMultiTileEntity) tileEntity; + } + + public void receiveCoverData(IMultiTileEntity mute, int aCover0, int aCover1, int aCover2, int aCover3, int aCover4, + int aCover5) { + boolean updated; + updated = mute.setCoverIDAtSideNoUpdate(ForgeDirection.DOWN, aCover0); + updated |= mute.setCoverIDAtSideNoUpdate(ForgeDirection.UP, aCover1); + updated |= mute.setCoverIDAtSideNoUpdate(ForgeDirection.NORTH, aCover2); + updated |= mute.setCoverIDAtSideNoUpdate(ForgeDirection.SOUTH, aCover3); + updated |= mute.setCoverIDAtSideNoUpdate(ForgeDirection.WEST, aCover4); + updated |= mute.setCoverIDAtSideNoUpdate(ForgeDirection.EAST, aCover5); + + if (updated) { + mute.issueBlockUpdate(); + } + } + + @Override + public TileEntity createNewTileEntity(World world, int meta) { + return registry.getNewTileEntity(meta); + } +} 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..325f583149 --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityClassContainer.java @@ -0,0 +1,208 @@ +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> registry; + private String localized; + private String categoryName; + + private final short muteID; + private final Class<? extends MultiTileEntity> muteClass; + private MultiTileEntity referenceTileEntity; + private NBTTagCompound parameters; + + public boolean hidden = false; + + public MultiTileEntityClassContainer(MultiTileEntityRegistry aRegistry, int aID, + Class<? extends MultiTileEntity> aClass) { + /* Start the Builder */ + registry = new WeakReference<>(aRegistry); + muteID = (short) aID; + muteClass = aClass; + parameters = new NBTTagCompound(); + } + + public boolean register() { + /* End and register the Builder with the registry */ + final MultiTileEntityRegistry registry = this.registry.get(); + + if (parameters.hasKey(NBT.MATERIAL) && !parameters.hasKey(NBT.COLOR)) parameters.setInteger( + NBT.COLOR, + GT_Util.getRGBInt( + Materials.get(parameters.getString(NBT.MATERIAL)) + .getRGBA())); + + try { + referenceTileEntity = muteClass.getDeclaredConstructor() + .newInstance(); + } catch (Throwable e) { + throw new IllegalArgumentException(e); + } + referenceTileEntity.initFromNBT(parameters, muteID, (short) -1); + + return registry != null && registry.add(this.localized, this) != null; + } + + public MultiTileEntityClassContainer name(String aName) { + localized = aName; + return this; + } + + public MultiTileEntityClassContainer category(String aCategoryName) { + categoryName = aCategoryName; + return this; + } + + public MultiTileEntityClassContainer hide() { + hidden = true; + 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 + parameters.setString(NBT.MATERIAL, material.toString()); + if (!parameters.hasKey(NBT.COLOR)) parameters.setInteger(NBT.COLOR, GT_Util.getRGBInt(material.getRGBA())); + return this; + } + + public MultiTileEntityClassContainer color(int rbg) { + parameters.setInteger(NBT.COLOR, rbg); + return this; + } + + public MultiTileEntityClassContainer color(short[] rgba) { + parameters.setInteger(NBT.COLOR, GT_Util.getRGBInt(rgba)); + return this; + } + + public MultiTileEntityClassContainer textureFolder(String texture) { + parameters.setString(NBT.TEXTURE_FOLDER, texture); + return this; + } + + public MultiTileEntityClassContainer inputInventorySize(int aSize) { + parameters.setInteger(NBT.INV_INPUT_SIZE, aSize); + return this; + } + + public MultiTileEntityClassContainer outputInventorySize(int aSize) { + parameters.setInteger(NBT.INV_OUTPUT_SIZE, aSize); + return this; + } + + public MultiTileEntityClassContainer tankCapacity(Long aCapacity) { + parameters.setLong(NBT.TANK_CAPACITY, aCapacity); + return this; + } + + public MultiTileEntityClassContainer tier(int aTier) { + verifyDescendentOfMultiple(true, UpgradeCasing.class, FunctionalCasing.class); + parameters.setInteger(NBT.TIER, aTier); + return this; + } + + public MultiTileEntityClassContainer upgradeInventorySize(int aSize) { + verifyDescendentOf(Inventory.class); + + parameters.setInteger(NBT.UPGRADE_INVENTORY_SIZE, aSize); + return this; + } + + public MultiTileEntityClassContainer upgradeTankCount(int count) { + verifyDescendentOf(Tank.class); + + parameters.setInteger(NBT.UPGRADE_TANK_COUNT, count); + return this; + } + + public MultiTileEntityClassContainer upgradeTankCapacity(Long aCapacity) { + parameters.setLong(NBT.UPGRADE_TANK_CAPACITY, aCapacity); + return this; + } + + public MultiTileEntityClassContainer upgradeAmperage(long amperage) { + parameters.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 + */ + parameters = GT_Util.fuseNBT(parameters, GT_Util.makeNBT(aTags)); + return this; + } + + public WeakReference<MultiTileEntityRegistry> getRegistry() { + return registry; + } + + public Class<? extends MultiTileEntity> getMuteClass() { + return muteClass; + } + + public short getMuteID() { + return muteID; + } + + public MultiTileEntity getReferenceTileEntity() { + return referenceTileEntity; + } + + public NBTTagCompound getParameters() { + return parameters; + } + + private void verifyDescendentOf(Class<?> cls) { + // Check if cls is extended by mClass + if (!cls.isAssignableFrom(muteClass)) { + throw new IllegalArgumentException( + "Expected a descendent of " + cls.getName() + " got " + muteClass.getName() + " instead."); + } + } + + private void verifyDescendentOfMultiple(boolean onlyOne, Class<?>... classes) { + boolean atLeastOne = false; + final StringBuilder classNames = new StringBuilder(); + for (Class<?> cls : classes) { + classNames.append(cls.getName()) + .append(" "); + if (!onlyOne) { + verifyDescendentOf(cls); + atLeastOne = true; + } else if (cls.isAssignableFrom(muteClass)) { + atLeastOne = true; + } + } + + if (!atLeastOne) { + throw new IllegalArgumentException( + "Expected a descendent of any of these " + classNames + " got " + muteClass.getName() + " instead."); + } + } +} diff --git a/src/main/java/gregtech/api/multitileentity/MultiTileEntityItem.java b/src/main/java/gregtech/api/multitileentity/MultiTileEntityItem.java new file mode 100644 index 0000000000..e67ab61e96 --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityItem.java @@ -0,0 +1,239 @@ +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.tileentity.TileEntity; +import net.minecraft.util.IIcon; +import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import gregtech.api.metatileentity.CoverableTileEntity; +import gregtech.api.multitileentity.interfaces.IMultiTileEntity; + +public class MultiTileEntityItem extends ItemBlock { + + public final MultiTileEntityBlock block; + + public MultiTileEntityItem(Block block) { + super(block); + setMaxDamage(0); + setHasSubtypes(true); + this.block = (MultiTileEntityBlock) block; + } + + @Override + @SuppressWarnings("unchecked") + public void addInformation(ItemStack stack, EntityPlayer player, List<String> list, boolean F3_H) { + final IMultiTileEntity mute = block.getRegistry() + .getReferenceTileEntity(stack); + if (mute == null) { + list.add("INVALID ITEM!"); + return; + } + try { + mute.addToolTips(list, stack, F3_H); + } catch (Throwable e) { + GT_FML_LOGGER.error("addInformation", e); + } + final NBTTagCompound aNBT = stack.getTagCompound(); + CoverableTileEntity.addInstalledCoversInformation(aNBT, list); + // TODO: Add anything else relevant + } + + @Override + @SideOnly(Side.CLIENT) + @SuppressWarnings("unchecked") + public void getSubItems(Item aItem, CreativeTabs aTab, List<ItemStack> aList) { + for (MultiTileEntityClassContainer tClass : block.getRegistry().registrations) { + if (!tClass.hidden && ((IMultiTileEntity) tClass.getReferenceTileEntity()) + .getSubItems(block, aItem, aTab, aList, tClass.getMuteID())) { + aList.add( + block.getRegistry() + .getItem(tClass.getMuteID())); + } + } + } + + @Override + public boolean onItemUse(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int ordinalSide, + float hitX, float hitY, float hitZ) { + + if (y < 0 || y > world.getHeight()) return false; + + if (player == null) return false; + + try { + ForgeDirection side = ForgeDirection.getOrientation(ordinalSide); + final Block clickedBlock = world.getBlock(x, y, z); + + if (clickedBlock instanceof BlockSnow && (world.getBlockMetadata(x, y, z) & 7) < 1) { + ordinalSide = SIDE_TOP; + side = ForgeDirection.UP; + } else if (clickedBlock != Blocks.vine && clickedBlock != Blocks.tallgrass + && clickedBlock != Blocks.deadbush + && !clickedBlock.isReplaceable(world, x, y, z)) { + x += side.offsetX; + y += side.offsetY; + z += side.offsetZ; + } + final Block tReplacedBlock = world.getBlock(x, y, z); + + if (!tReplacedBlock.isReplaceable(world, x, y, z) + || !block.canReplace(world, x, y, z, ordinalSide, stack)) { + return false; + } + + if (stack.stackSize == 0 || (!player.canPlayerEdit(x, y, z, ordinalSide, stack))) { + return false; + } + // final TileEntity tileEntity = block.getRegistry().getNewTileEntity(world, x, y, z, stack); + + if (!world.setBlock(x, y, z, block, Items.feather.getDamage(stack), 2)) { + return false; + } + final TileEntity te = world.getTileEntity(x, y, z); + if (!(te instanceof IMultiTileEntity mute)) { + throw new IllegalStateException("TileEntity is not an IMultiTileEntity"); + } + // MuTEContainer.setMultiTile(world, x, y, z); + + try { + if (mute.onPlaced(stack, player, world, x, y, z, side, hitX, hitY, hitZ)) { + world.playSoundEffect( + x + 0.5, + y + 0.5, + z + 0.5, + block.stepSound.func_150496_b(), + (block.stepSound.getVolume() + 1) / 2, + block.stepSound.getPitch() * 0.8F); + } + } catch (Throwable e) { + GT_FML_LOGGER.error("onPlaced", e); + } + // spotless:off + /* + * Used to be behind `IMTE_HasMultiBlockMachineRelevantData` + * Add back if needed + */ + // try { + // GregTech_API.causeMachineUpdate(world, x, y, z); + // } catch (Throwable e) { + // GT_FML_LOGGER.error("causeMachineUpdate", e); + // } + // spotless:on + try { + if (!world.isRemote) { + world.notifyBlockChange(x, y, z, tReplacedBlock); + world.func_147453_f /* updateNeighborsAboutBlockChange */(x, y, z, block); + } + } catch (Throwable e) { + GT_FML_LOGGER.error("notifyBlockChange", e); + } + try { + mute.onTileEntityPlaced(); + } catch (Throwable e) { + GT_FML_LOGGER.error("onTileEntityPlaced", e); + } + try { + world.func_147451_t /* updateAllLightTypes */(x, y, z); + } catch (Throwable e) { + GT_FML_LOGGER.error("updateAllLightTypes", e); + } + + stack.stackSize--; + return true; + + } catch (Throwable e) { + GT_FML_LOGGER.error("onItemUse", e); + } + return false; + } + + @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 block.getRegistry() + .getInternalName(); + } + + @Override + public final String getUnlocalizedName(ItemStack aStack) { + return block.getRegistry() + .getInternalName() + "." + + 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..0b5afbb043 --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/MultiTileEntityRegistry.java @@ -0,0 +1,232 @@ +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 net.minecraft.block.Block; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +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 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; +import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; + +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<>(); + + public HashMap<Short, CreativeTab> creativeTabs = new HashMap<>(); + public final Short2ObjectMap<MultiTileEntityClassContainer> registry = new Short2ObjectOpenHashMap<>(); + public List<MultiTileEntityClassContainer> registrations = new ArrayList<>(); + + private final String internalName; + private final MultiTileEntityBlock block; + + /** + * @param internalName the internal Name of the Item + */ + public MultiTileEntityRegistry(String internalName, MultiTileEntityBlock block) { + if (!Loader.instance() + .isInState(LoaderState.PREINITIALIZATION)) { + throw new IllegalStateException( + "The MultiTileEntity Registry must be initialized during Preload Phase and not before"); + } + if (!block.isRegistered()) { + throw new IllegalStateException("Block not registered"); + } + this.internalName = internalName; + this.block = block; + GT_FML_LOGGER.info(internalName + " " + Block.getIdFromBlock(block) + " This is the answer"); + this.block.setRegistry(this); + REGISTRIES.put(new ItemStack(Item.getItemById(Block.getIdFromBlock(block)), 1, GT_Values.W), this); + NAMED_REGISTRIES.put(internalName, this); + } + + public static TileEntity getReferenceTileEntity(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.getReferenceTileEntity(); + } + + public MultiTileEntity getReferenceTileEntity(ItemStack stack) { + return getReferenceTileEntity(Items.feather.getDamage(stack)); + } + + public MultiTileEntity getReferenceTileEntity(int metaId) { + final MultiTileEntityClassContainer muteClass = registry.get((short) metaId); + if (muteClass == null) return null; + return muteClass.getReferenceTileEntity(); + } + + 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, 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.getMuteClass() == null) { + GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: Class inside Class Container is null!"); + tFailed = true; + } + if (aClassContainer.getMuteID() == GT_Values.W) { + GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: Class Container uses Wildcard MetaData!"); + tFailed = true; + } + if (aClassContainer.getMuteID() < 0) { + GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR: Class Container uses negative MetaData!"); + tFailed = true; + } + if (registry.containsKey(aClassContainer.getMuteID())) { + GT_FML_LOGGER.error( + "MULTI-TILE REGISTRY ERROR: Class Container uses occupied MetaData! (" + aClassContainer.getMuteID() + + ")"); + tFailed = true; + } + } + if (tFailed) { + GT_FML_LOGGER.error("MULTI-TILE REGISTRY ERROR " + aLocalised + " : 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(internalName + "." + aClassContainer.getMuteID() + ".name", aLocalised); + registry.put(aClassContainer.getMuteID(), aClassContainer); + mLastRegisteredID = aClassContainer.getMuteID(); + registrations.add(aClassContainer); + + if (sRegisteredTileEntities.add( + aClassContainer.getReferenceTileEntity() + .getClass())) { + aClassContainer.getReferenceTileEntity() + .onRegistrationFirst(this, aClassContainer.getMuteID()); + } + + return getItem(aClassContainer.getMuteID()); + } + + 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 nbt) { + return getItem(aID, 1, nbt); + } + + public ItemStack getItem(int aID, long aAmount) { + return getItem(aID, aAmount, null); + } + + public ItemStack getItem(int metaID, long amount, NBTTagCompound nbt) { + final ItemStack stack = new ItemStack(block, (int) amount, metaID); + if (nbt == null || nbt.hasNoTags()) { + nbt = new NBTTagCompound(); + final TileEntity tileEntity = getNewTileEntity(metaID, nbt); + ((IMultiTileEntity) tileEntity).writeItemNBT(nbt); + } + stack.setTagCompound(nbt); + return stack; + } + + public String getLocal(int aID) { + return StatCollector.translateToLocal(internalName + "." + aID + ".name"); + } + + public MultiTileEntityClassContainer getClassContainer(int aID) { + return registry.get((short) aID); + } + + public MultiTileEntityClassContainer getClassContainer(ItemStack stack) { + return registry.get((short) Items.feather.getDamage(stack)); + } + + public TileEntity getNewTileEntity(int aID) { + return getNewTileEntity(null, 0, 0, 0, aID); + } + + public TileEntity getNewTileEntity(World aWorld, int x, int y, int z, int metaID) { + return getNewTileEntity(aWorld, x, y, z, metaID, null); + } + + public TileEntity getNewTileEntity(World aWorld, int x, int y, int z, int metaID, NBTTagCompound nbt) { + final MultiTileEntityClassContainer container = registry.get((short) metaID); + if (container == null) return null; + final MultiTileEntity te = (MultiTileEntity) GT_Utility + .callConstructor(container.getMuteClass(), -1, null, true); + te.setWorldObj(aWorld); + te.xCoord = x; + te.yCoord = y; + te.zCoord = z; + nbt = (nbt == null || nbt.hasNoTags()) ? container.getParameters() + : GT_Util.fuseNBT(nbt, container.getParameters()); + te.initFromNBT(nbt, (short) metaID, (short) Block.getIdFromBlock(block)); + return te; + } + + public TileEntity getNewTileEntity(int meta, NBTTagCompound nbt) { + return getNewTileEntity(null, 0, 0, 0, meta, nbt); + } + + public String getInternalName() { + return internalName; + } + + public MultiTileEntityBlock getBlock() { + return block; + } +} diff --git a/src/main/java/gregtech/api/multitileentity/WeakTargetRef.java b/src/main/java/gregtech/api/multitileentity/WeakTargetRef.java new file mode 100644 index 0000000000..1509384f5e --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/WeakTargetRef.java @@ -0,0 +1,88 @@ +package gregtech.api.multitileentity; + +import java.lang.ref.WeakReference; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ChunkCoordinates; +import net.minecraft.world.World; + +import gregtech.api.multitileentity.interfaces.IMultiTileEntity; + +public class WeakTargetRef<T extends IMultiTileEntity> { + + protected final ChunkCoordinates position = new ChunkCoordinates(0, -1, 0); + protected final Class<?> targetClass; + protected boolean shouldCache; + protected WeakReference<T> target = new WeakReference<>(null); + protected World world = null; + + public WeakTargetRef(Class<?> targetClass, boolean shouldCache) { + this.targetClass = targetClass; + this.shouldCache = shouldCache; + } + + public WeakTargetRef(T target, boolean shouldCache) { + this(target.getClass(), shouldCache); + setTarget(target); + } + + public void setTarget(T newTarget) { + if (!targetClass.isInstance(newTarget)) { + throw new IllegalArgumentException("Target is not of the correct type"); + } + position.set(newTarget.getXCoord(), newTarget.getYCoord(), newTarget.getZCoord()); + world = newTarget.getWorld(); + } + + public void setWorld(World world) { + this.world = world; + } + + public void setPosition(ChunkCoordinates position) { + this.position.set(position.posX, position.posY, position.posZ); + } + + public void setPosition(int x, int y, int z) { + this.position.set(x, y, z); + } + + public void setShouldCache(boolean shouldCache) { + this.shouldCache = shouldCache; + } + + public T get() { + if (!shouldCache) { + return resolveTarget(); + } + T result = target.get(); + if (result == null || result.isDead()) { + result = resolveTarget(); + if (result != null) { + target = new WeakReference<>(result); + } else { + target.clear(); + } + } + return result; + } + + @SuppressWarnings("unchecked") + protected T resolveTarget() { + if (world != null && position.posX >= 0 && world.blockExists(position.posX, position.posY, position.posZ)) { + final TileEntity te = world.getTileEntity(position.posX, position.posY, position.posZ); + return this.targetClass.isInstance(te) ? (T) te : null; + } + return null; + } + + public ChunkCoordinates getPosition() { + return position; + } + + public void invalidate() { + target.clear(); + world = null; + position.set(0, -1, 0); + } + +} 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..eeadfe7602 --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/base/MultiTileEntity.java @@ -0,0 +1,1385 @@ +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.MultiTileEntityBlock; +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, 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 muteID, short muteRegistry) { + if (this.mteID == muteID && this.mteRegistry == muteRegistry) { + return; + } + // Set ID and Registry ID. + this.mteID = muteID; + this.mteRegistry = muteRegistry; + // 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 referenceTileEntity = MultiTileEntityRegistry + .getReferenceTileEntity(getMultiTileEntityRegistryID(), getMultiTileEntityID()); + if (!(referenceTileEntity instanceof MultiTileEntity canonicalEntity)) { + return; + } + 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.getParameters()); + } + } + } + // 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 nbt) { + super.writeToNBT(nbt); + // write the IDs + nbt.setShort(NBT.MTE_ID, mteID); + nbt.setShort(NBT.MTE_REG, mteRegistry); + // write the Custom Name + if (GT_Utility.isStringValid(customName)) { + final NBTTagCompound displayNBT; + if (nbt.hasKey(NBT.DISPLAY)) { + displayNBT = nbt.getCompoundTag(NBT.DISPLAY); + } else { + displayNBT = new NBTTagCompound(); + nbt.setTag(NBT.DISPLAY, displayNBT); + } + displayNBT.setString(NBT.CUSTOM_NAME, customName); + } + + // write the rest + try { + nbt.setString(NBT.OWNER, ownerName); + nbt.setString(NBT.OWNER_UUID, ownerUUID == null ? "" : ownerUUID.toString()); + nbt.setBoolean(NBT.LOCK_UPGRADE, lockUpgrade); + nbt.setInteger(NBT.FACING, facing.ordinal()); + + writeCoverNBT(nbt, false); + writeTasksNBT(nbt); + writeMultiTileNBT(nbt); + } 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.providesStrongPower()) { + 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 onBlockBroken() { + isDead = true; + onBaseTEDestroyed(); + return false; + } + + @Override + public boolean getSubItems(MultiTileEntityBlock block, Item item, CreativeTabs tab, List<ItemStack> list, + 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()); + } + + @Override + public void onChunkUnload() { + super.onChunkUnload(); + } +} 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..987a4c18b3 --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/base/TickableMultiTileEntity.java @@ -0,0 +1,172 @@ +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.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 { + + /** 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/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..f5bbbec7ab --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiBlockPart.java @@ -0,0 +1,33 @@ +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 setLockedInventoryIndex(int aIndex); + + int getLockedInventoryIndex(); + + UUID getLockedInventory(); + + boolean tickCoverAtSide(ForgeDirection side, long aTickTimer); + + boolean shouldTick(long tickTimer); + + int getMode(); + + void setMode(int mode); + + int getAllowedModes(); + + void setAllowedModes(int allowedModes); + +} 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..b16ca8a376 --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/interfaces/IMultiTileEntity.java @@ -0,0 +1,237 @@ +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 gregtech.api.interfaces.tileentity.ICoverable; +import gregtech.api.interfaces.tileentity.IDebugableTileEntity; +import gregtech.api.interfaces.tileentity.ITurnable; +import gregtech.api.multitileentity.MultiTileEntityBlock; +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(MultiTileEntityBlock block, Item item, CreativeTabs tab, List<ItemStack> list, 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); + + /** return true to prevent the TileEntity from being removed. */ + boolean onBlockBroken(); + + default void onNeighborBlockChange(World aWorld, Block aBlock) { + /* Do Nothing */ + } + + // ItemStack getPickBlock(MovingObjectPosition aTarget); + + /** Remember that it passes the opposite Side due to the way vanilla works! */ + default int isProvidingWeakPower(ForgeDirection oppositeSide) { + return 0; + } + + default boolean providesStrongPower() { + return false; + } + + /** Remember that it passes the opposite Side due to the way vanilla works! */ + default int isProvidingStrongPower(ForgeDirection oppositeSide) { + return 0; + } + + default boolean shouldCheckWeakPower(ForgeDirection side) { + return false; + } + + default boolean getWeakChanges() { + return false; + } + + default boolean hasComparatorInputOverride() { + return false; + } + + default int getComparatorInputOverride(ForgeDirection side) { + return 0; + } + + default float getBlockHardness() { + return 1.0f; + } + + /** Adds ToolTips to the Item. */ + default void addToolTips(List<String> aList, ItemStack aStack, boolean aF3_H) { + // Do nothing + } + + // interface IMTE_HasMultiBlockMachineRelevantData extends IMultiTileEntity { + // + // /** Return true to mark this Block as a Machine Block for Multiblocks. (Triggers machine update thread) */ + // boolean hasMultiBlockMachineRelevantData(); + // } + +} 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..0953b4905c --- /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 + .getReferenceTileEntity(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..552cf6d94e --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java @@ -0,0 +1,1082 @@ +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.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +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 java.util.stream.Stream; + +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.jetbrains.annotations.NotNull; +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.WeakTargetRef; +import gregtech.api.multitileentity.enums.MultiTileCasingPurpose; +import gregtech.api.multitileentity.interfaces.IMultiBlockController; +import gregtech.api.multitileentity.interfaces.IMultiBlockPart; +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, 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<WeakTargetRef<UpgradeCasing>> upgradeCasings = new ArrayList<>(); + private final List<WeakTargetRef<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<List<WeakTargetRef<IMultiBlockPart>>> registeredCoveredParts = Arrays.asList( + new ArrayList<>(), + new ArrayList<>(), + new ArrayList<>(), + new ArrayList<>(), + new ArrayList<>(), + new ArrayList<>()); + + // A list for each purpose that a casing can register to, to be ticked + protected List<List<WeakTargetRef<IMultiBlockPart>>> registeredTickableParts = new ArrayList<>(); + + public Controller() { + for (int i = 0; i < MultiTileCasingPurpose.values().length; i++) { + registeredTickableParts.add(new ArrayList<>()); + } + } + + /** 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() { + if (functionalCasings.size() == 0) { + return; + } + double sum = 0; + for (WeakTargetRef<FunctionalCasing> casingRef : functionalCasings) { + final FunctionalCasing casing = casingRef.get(); + if (casing == null) continue; + 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 List<WeakTargetRef<IMultiBlockPart>> registeredCovers = registeredCoveredParts.get(side.ordinal()); + // TODO: Make sure that we're not already registered on this side + registeredCovers.add(new WeakTargetRef<>(part, true)); + } + + @Override + public void unregisterCoveredPartOnSide(final ForgeDirection side, IMultiBlockPart aPart) { + if (side == ForgeDirection.UNKNOWN) return; + + final List<WeakTargetRef<IMultiBlockPart>> coveredParts = registeredCoveredParts.get(side.ordinal()); + final Iterator<WeakTargetRef<IMultiBlockPart>> it = coveredParts.listIterator(); + 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 List<WeakTargetRef<IMultiBlockPart>> tickableParts = registeredTickableParts.get(purpose.ordinal()); + final Iterator<WeakTargetRef<IMultiBlockPart>> it = tickableParts.listIterator(); + while (it.hasNext()) { + final IMultiBlockPart next = (it.next()).get(); + if (next == null) { + it.remove(); + } else if (next == part) { + return; + } + } + tickableParts.add(new WeakTargetRef<>(part, true)); + } + + @Override + public void unregisterCaseWithPurpose(MultiTileCasingPurpose purpose, IMultiBlockPart part) { + final List<WeakTargetRef<IMultiBlockPart>> tickableParts = registeredTickableParts.get(purpose.ordinal()); + final Iterator<WeakTargetRef<IMultiBlockPart>> it = tickableParts.listIterator(); + 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 List<WeakTargetRef<IMultiBlockPart>> coveredParts = this.registeredCoveredParts.get(side.ordinal()); + final Iterator<WeakTargetRef<IMultiBlockPart>> it = coveredParts.listIterator(); + 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) { + tickCovers(); + } + + @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 List<WeakTargetRef<IMultiBlockPart>> registeredItemOutputs = registeredTickableParts + .get(MultiTileCasingPurpose.ItemOutput.ordinal()); + final Iterator<WeakTargetRef<IMultiBlockPart>> itemOutputIterator = registeredItemOutputs.listIterator(); + 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++) { + final ItemStack stack = part.getStackInSlot(i); + if (stack != null && stack.stackSize <= 0) { + part.setInventorySlotContents(i, null); + } + } + + } + } + + protected void pushFluidOutputs(long tick) { + if (tick % AUTO_OUTPUT_FREQUENCY_TICK != 0) return; + final List<WeakTargetRef<IMultiBlockPart>> registeredFluidOutputs = registeredTickableParts + .get(MultiTileCasingPurpose.FluidOutput.ordinal()); + final Iterator<WeakTargetRef<IMultiBlockPart>> fluidOutputIterator = registeredFluidOutputs.listIterator(); + 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 upgradeCasing) { + upgradeCasings.add(new WeakTargetRef<>(upgradeCasing, true)); + } + if (part instanceof FunctionalCasing functionalCasing) { + functionalCasings.add(new WeakTargetRef<>(functionalCasing, true)); + } + } + + // #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; + }; + } + + @Override + @Nonnull + public @NotNull FluidInventoryLogic getFluidLogic(@Nonnull InventoryType type, @Nullable UUID id) { + return switch (type) { + case Input -> controllerFluidInput.getInventoryLogic(id); + case Output -> controllerFluidOutput.getInventoryLogic(id); + default -> throw new IllegalStateException("Unexpected value: " + type); + }; + } + + @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( + Stream.of(input, output) + .map(FluidInventoryLogic::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 + @Nonnull + public ItemInventoryLogic getItemLogic(@Nonnull InventoryType type, @Nullable UUID id) { + return switch (type) { + case Input -> controllerItemInput.getInventoryLogic(id); + case Output -> controllerItemOutput.getInventoryLogic(id); + default -> throw new IllegalStateException("Unexpected value: " + type); + }; + } + + @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..5a16ed4b38 --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java @@ -0,0 +1,693 @@ +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.WeakTargetRef; +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.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, PowerLogicHost, 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 final WeakTargetRef<IMultiBlockController> controller = new WeakTargetRef<>( + IMultiBlockController.class, + false); + + 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 newController, int aAllowedModes) { + final IMultiBlockController currentController = getTarget(false); + if (currentController != null && currentController != newController) { + for (MultiTileCasingPurpose purpose : registeredPurposes) { + unregisterPurpose(purpose); + } + } + + allowedModes = aAllowedModes; + if (newController != currentController) { + registerCovers(newController); + 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) { + final IMultiBlockController res = controller.get(); + if (res != null && aCheckValidity) { + return res.checkStructure(false) ? res : null; + } + return res; + } + + 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)) { + controller + .setPosition(aNBT.getInteger(NBT.TARGET_X), aNBT.getShort(NBT.TARGET_Y), aNBT.getInteger(NBT.TARGET_Z)); + controller.setWorld(worldObj); + } + 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 nbt) { + if (allowedModes != NOTHING) nbt.setInteger(NBT.ALLOWED_MODES, allowedModes); + if (mode != 0) nbt.setInteger(NBT.MODE, mode); + + final ChunkCoordinates pos = controller.getPosition(); + if (pos.posY >= 0) { + // Valid position + nbt.setBoolean(NBT.TARGET, true); + nbt.setInteger(NBT.TARGET_X, pos.posX); + nbt.setShort(NBT.TARGET_Y, (short) pos.posY); + nbt.setInteger(NBT.TARGET_Z, pos.posZ); + } + + if (lockedInventory != null) { + nbt.setString(NBT.LOCKED_INVENTORY, lockedInventory.toString()); + } + if (mLockedInventoryIndex != 0) { + nbt.setInteger(NBT.LOCKED_INVENTORY_INDEX, mLockedInventoryIndex); + } + configurationTank.writeToNBT(nbt, NBT.LOCKED_FLUID); + } + + @Override + public void setLockedInventoryIndex(int index) { + mLockedInventoryIndex = index; + } + + @Override + public int getLockedInventoryIndex() { + return mLockedInventoryIndex; + } + + @Override + public ChunkCoordinates getTargetPos() { + return controller.getPosition(); + } + + @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 `mode` is one of the allowed modes + */ + public boolean hasMode(int mode) { + // This is not sent to the client + return (allowedModes & mode) != 0; + } + + /** + * Returns true if the part has any of the modes provided, and that mode is the currently selected mode + */ + public boolean modeSelected(int... modes) { + for (int mode : modes) { + if (hasMode(mode) && this.mode == getModeOrdinal(mode)) return true; + } + return false; + } + + @Override + public boolean onBlockBroken() { + 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..238ce1eb2d --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/WallShareablePart.java @@ -0,0 +1,100 @@ +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.WeakTargetRef; +import gregtech.api.multitileentity.interfaces.IMultiBlockController; + +public class WallShareablePart extends MultiBlockPart { + + protected List<WeakTargetRef<IMultiBlockController>> targets = new ArrayList<>(); + + @Override + public void setTarget(IMultiBlockController newController, int allowedModes) { + if (targets.size() >= 1) { + this.allowedModes = 0; + setMode((byte) 0); + controller.invalidate(); + } else { + this.allowedModes = allowedModes; + controller.setTarget(newController); + } + + if (newController == null) { + return; + } + + targets.add(new WeakTargetRef<IMultiBlockController>(IMultiBlockController.class, true)); + } + + @Override + public UUID getLockedInventory() { + if (targets.size() > 1) { + return null; + } + return super.getLockedInventory(); + } + + @Override + public IMultiBlockController getTarget(boolean aCheckValidity) { + if (targets.size() != 1) { + return null; + } + + controller.setTarget( + targets.get(0) + .get()); + return super.getTarget(aCheckValidity); + } + + @Override + public String getTileEntityName() { + return "gt.multiTileEntity.casing.wallSharable"; + } + + @Override + public boolean onBlockBroken() { + for (final WeakTargetRef<IMultiBlockController> tar : targets) { + IMultiBlockController target = getTarget(tar.getPosition(), 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 tController) { + tController.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..566afcd770 --- /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 newController, int aAllowedModes) { + super.setTarget(newController, 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); + +} |