package com.carpentersblocks.block; import java.util.ArrayList; import java.util.List; import java.util.Random; import net.minecraft.block.Block; import net.minecraft.block.BlockContainer; import net.minecraft.block.BlockDirectional; import net.minecraft.block.BlockRedstoneWire; import net.minecraft.block.material.Material; import net.minecraft.client.particle.EffectRenderer; import net.minecraft.client.renderer.texture.IIconRegister; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.boss.EntityDragon; import net.minecraft.entity.boss.EntityWither; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.Direction; import net.minecraft.util.IIcon; import net.minecraft.util.MathHelper; import net.minecraft.util.MovingObjectPosition; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; import net.minecraftforge.client.ForgeHooksClient; import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.common.IPlantable; import net.minecraftforge.common.util.ForgeDirection; import com.carpentersblocks.api.ICarpentersChisel; import com.carpentersblocks.api.ICarpentersHammer; import com.carpentersblocks.api.IWrappableBlock; import com.carpentersblocks.renderer.helper.ParticleHelper; import com.carpentersblocks.renderer.helper.RoutableFluidsHelper; import com.carpentersblocks.tileentity.TEBase; import com.carpentersblocks.util.BlockProperties; import com.carpentersblocks.util.EntityLivingUtil; import com.carpentersblocks.util.handler.DesignHandler; import com.carpentersblocks.util.handler.EventHandler; import com.carpentersblocks.util.handler.OverlayHandler; import com.carpentersblocks.util.handler.OverlayHandler.Overlay; import com.carpentersblocks.util.protection.PlayerPermissions; import com.carpentersblocks.util.protection.ProtectedObject; import com.carpentersblocks.util.registry.FeatureRegistry; import com.carpentersblocks.util.registry.IconRegistry; import com.carpentersblocks.util.registry.ItemRegistry; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; public class BlockCoverable extends BlockContainer { /** Block drop event for dropping attribute. */ public static int EVENT_ID_DROP_ATTR = 0; /** Indicates during getDrops that block instance should not be dropped. */ protected final int METADATA_DROP_ATTR_ONLY = 16; /** * Stores actions taken on a block in order to properly play sounds, * decrement player inventory, and to determine if a block was altered. */ protected class ActionResult { public ItemStack itemStack; public boolean playSound = true; public boolean altered = false; public boolean decInv = false; public ActionResult setSoundSource(ItemStack itemStack) { this.itemStack = itemStack; return this; } public ActionResult setNoSound() { playSound = false; return this; } public ActionResult setAltered() { altered = true; return this; } public ActionResult decInventory() { decInv = true; return this; } } /** * Class constructor. * * @param material */ public BlockCoverable(Material material) { super(material); } @SideOnly(Side.CLIENT) @Override /** * When this method is called, your block should register all the icons it needs with the given IconRegister. This * is the only chance you get to register icons. */ public void registerBlockIcons(IIconRegister iconRegister) { } @SideOnly(Side.CLIENT) /** * Returns a base icon that doesn't rely on blockIcon, which * is set prior to texture stitch events. * * @return default icon */ public IIcon getIcon() { return IconRegistry.icon_uncovered_solid; } @SideOnly(Side.CLIENT) @Override /** * Returns the icon on the side given the block metadata. * <p> * Due to the amount of control needed over this, vanilla calls will always return an invisible icon. */ public IIcon getIcon(int side, int metadata) { if (BlockProperties.isMetadataDefaultIcon(metadata)) { return getIcon(); } /* * This icon is a mask (or something) for redstone wire. * We use it here because it renders an invisible icon. * * Using an invisible icon is important because sprint particles are * hard-coded and will always grab particle icons using this method. * We'll throw our own sprint particles in EventHandler.class. */ return BlockRedstoneWire.getRedstoneWireIcon("cross_overlay"); } @SideOnly(Side.CLIENT) @Override /** * Retrieves the block texture to use based on the display side. Args: iBlockAccess, x, y, z, side */ public IIcon getIcon(IBlockAccess blockAccess, int x, int y, int z, int side) { TEBase TE = getTileEntity(blockAccess, x, y, z); ItemStack itemStack = BlockProperties.getCover(TE, 6); Block block = BlockProperties.toBlock(itemStack); return block instanceof BlockCoverable ? getIcon() : getWrappedIcon(block, blockAccess, x, y, z, side, itemStack.getItemDamage()); } private static IIcon getWrappedIcon(Block b, IBlockAccess iba, int x, int y, int z, int side, int meta) { return b instanceof IWrappableBlock ? ((IWrappableBlock)b).getIcon(iba, x, y, z, side, b, meta) : b.getIcon(side, meta); } /** * For South-sided blocks, rotates and sets the block bounds using * the provided ForgeDirection. * * @param dir the rotated {@link ForgeDirection} */ protected void setBlockBounds(float minX, float minY, float minZ, float maxX, float maxY, float maxZ, ForgeDirection dir) { switch (dir) { case DOWN: setBlockBounds(minX, 1.0F - maxZ, minY, maxX, 1.0F - minZ, maxY); break; case UP: setBlockBounds(minX, minZ, minY, maxX, maxZ, maxY); break; case NORTH: setBlockBounds(1.0F - maxX, minY, 1.0F - maxZ, 1.0F - minX, maxY, 1.0F - minZ); break; case EAST: setBlockBounds(minZ, minY, 1.0F - maxX, maxZ, maxY, 1.0F - minX); break; case WEST: setBlockBounds(1.0F - maxZ, minY, minX, 1.0F - minZ, maxY, maxX); break; default: setBlockBounds(minX, minY, minZ, maxX, maxY, maxZ); break; } } /** * Called when block event is received. *<p> * For the context of this mod, this is used for dropping block attributes * like covers, overlays, dyes, or any other ItemStack. *<p> * In order for external classes to call the protected method * {@link Block#dropBlockAsItem(World,int,int,int,ItemStack) dropBlockAsItem}, * they create a block event with parameters itemId and metadata, allowing * the {@link ItemStack} to be recreated and dropped. * * @param world the {@link World} * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @param itemId the eventId, repurposed * @param metadata the event parameter, repurposed * @return true if event was handled */ @Override public boolean onBlockEventReceived(World world, int x, int y, int z, int eventId, int param /*attrId*/) { if (!world.isRemote && eventId == EVENT_ID_DROP_ATTR) { TEBase TE = getSimpleTileEntity(world, x, y, z); if (TE != null && TE.hasAttribute((byte) param)) { ItemStack itemStack = TE.getAttributeForDrop((byte) param); dropBlockAsItem(world, x, y, z, itemStack); TE.onAttrDropped((byte) param); return true; } } return super.onBlockEventReceived(world, x, y, z, eventId, param); } /** * Returns an item stack containing a single instance of the current block type. 'i' is the block's subtype/damage * and is ignored for blocks which do not support subtypes. Blocks which cannot be harvested should return null. */ protected ItemStack getItemDrop(World world, int metadata) { int fortune = 1; return new ItemStack(getItemDropped(metadata, world.rand, fortune), 1, metadata); } /** * Returns adjacent, similar tile entities that can be used for duplicating * block properties like dye color, pattern, style, etc. * * @param world the world reference * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @return an array of adjacent, similar tile entities * @see {@link TEBase} */ protected TEBase[] getAdjacentTileEntities(World world, int x, int y, int z) { return new TEBase[] { getSimpleTileEntity(world, x, y - 1, z), getSimpleTileEntity(world, x, y + 1, z), getSimpleTileEntity(world, x, y, z - 1), getSimpleTileEntity(world, x, y, z + 1), getSimpleTileEntity(world, x - 1, y, z), getSimpleTileEntity(world, x + 1, y, z) }; } /** * Returns tile entity if block tile entity is instanceof TEBase. * * Used for generic purposes such as getting pattern, dye color, or * cover of another Carpenter's block. Is also used if block * no longer exists, such as when breaking a block and ejecting * attributes. */ protected TEBase getSimpleTileEntity(IBlockAccess blockAccess, int x, int y, int z) { TileEntity TE = blockAccess.getTileEntity(x, y, z); return (TE instanceof TEBase) ? (TEBase) TE : null; } /** * Returns tile entity if block tile entity is instanceof TEBase and * also belongs to this block type. */ protected TEBase getTileEntity(IBlockAccess blockAccess, int x, int y, int z) { TEBase TE = getSimpleTileEntity(blockAccess, x, y, z); return TE != null && blockAccess.getBlock(x, y, z).equals(this) ? TE : null; } /** * Returns whether player is allowed to activate this block. */ protected boolean canPlayerActivate(TEBase TE, EntityPlayer entityPlayer) { return true; } @Override /** * Called when the block is clicked by a player. Args: x, y, z, entityPlayer */ public void onBlockClicked(World world, int x, int y, int z, EntityPlayer entityPlayer) { if (world.isRemote) { return; } TEBase TE = getTileEntity(world, x, y, z); if (TE == null) { return; } else if (!PlayerPermissions.hasElevatedPermission(TE, entityPlayer, false)) { return; } ItemStack itemStack = entityPlayer.getCurrentEquippedItem(); if (itemStack == null) { return; } int effectiveSide = TE.hasAttribute(TE.ATTR_COVER[EventHandler.eventFace]) ? EventHandler.eventFace : 6; Item item = itemStack.getItem(); if (item instanceof ICarpentersHammer && ((ICarpentersHammer)item).canUseHammer(world, entityPlayer)) { ActionResult actionResult = new ActionResult(); preOnBlockClicked(TE, world, x, y, z, entityPlayer, actionResult); if (!actionResult.altered) { if (entityPlayer.isSneaking()) { popAttribute(TE, effectiveSide); } else { onHammerLeftClick(TE, entityPlayer); } actionResult.setAltered(); } else { onNeighborBlockChange(world, x, y, z, this); world.notifyBlocksOfNeighborChange(x, y, z, this); } } else if (item instanceof ICarpentersChisel && ((ICarpentersChisel)item).canUseChisel(world, entityPlayer)) { if (entityPlayer.isSneaking() && TE.hasChiselDesign(effectiveSide)) { TE.removeChiselDesign(effectiveSide); } else if (TE.hasAttribute(TE.ATTR_COVER[effectiveSide])) { onChiselClick(TE, effectiveSide, true); } } } /** * Pops attribute in hard-coded order. * * @param TE * @param side */ private void popAttribute(TEBase TE, int side) { if (TE.hasAttribute(TE.ATTR_ILLUMINATOR)) { TE.createBlockDropEvent(TE.ATTR_ILLUMINATOR); } else if (TE.hasAttribute(TE.ATTR_OVERLAY[side])) { TE.createBlockDropEvent(TE.ATTR_OVERLAY[side]); } else if (TE.hasAttribute(TE.ATTR_DYE[side])) { TE.createBlockDropEvent(TE.ATTR_DYE[side]); } else if (TE.hasAttribute(TE.ATTR_COVER[side])) { TE.removeChiselDesign(side); TE.createBlockDropEvent(TE.ATTR_COVER[side]); } } @Override /** * Called upon block activation (right click on the block.) */ public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer entityPlayer, int side, float hitX, float hitY, float hitZ) { if (world.isRemote) { return true; } TEBase TE = getTileEntity(world, x, y, z); if (TE == null) { return false; } if (!canPlayerActivate(TE, entityPlayer)) { return false; } // Allow block to change TE if needed before altering attributes TE = getTileEntityForBlockActivation(TE); ActionResult actionResult = new ActionResult(); preOnBlockActivated(TE, entityPlayer, side, hitX, hitY, hitZ, actionResult); // If no prior event occurred, try regular activation if (!actionResult.altered) { if (PlayerPermissions.hasElevatedPermission(TE, entityPlayer, false)) { ItemStack itemStack = entityPlayer.getCurrentEquippedItem(); if (itemStack != null) { /* Sides 0-5 are side covers, and 6 is the base block. */ int effectiveSide = TE.hasAttribute(TE.ATTR_COVER[side]) ? side : 6; if (itemStack.getItem() instanceof ICarpentersHammer && ((ICarpentersHammer)itemStack.getItem()).canUseHammer(world, entityPlayer)) { if (onHammerRightClick(TE, entityPlayer)) { actionResult.setAltered(); } } else if (ItemRegistry.enableChisel && itemStack.getItem() instanceof ICarpentersChisel && ((ICarpentersChisel)itemStack.getItem()).canUseChisel(world, entityPlayer)) { if (TE.hasAttribute(TE.ATTR_COVER[effectiveSide])) { if (onChiselClick(TE, effectiveSide, false)) { actionResult.setAltered(); } } } else if (FeatureRegistry.enableCovers && BlockProperties.isCover(itemStack)) { Block block = BlockProperties.toBlock(itemStack); /* Will handle blocks that save directions using only y axis (pumpkin) */ int metadata = block instanceof BlockDirectional ? MathHelper.floor_double(entityPlayer.rotationYaw * 4.0F / 360.0F + 2.5D) & 3 : itemStack.getItemDamage(); /* Will handle blocks that save directions using all axes (logs, quartz) */ if (BlockProperties.blockRotates(itemStack)) { int rot = Direction.rotateOpposite[EntityLivingUtil.getRotationValue(entityPlayer)]; int side_interpolated = entityPlayer.rotationPitch < -45.0F ? 0 : entityPlayer.rotationPitch > 45 ? 1 : rot == 0 ? 3 : rot == 1 ? 4 : rot == 2 ? 2 : 5; metadata = block.onBlockPlaced(world, TE.xCoord, TE.yCoord, TE.zCoord, side_interpolated, hitX, hitY, hitZ, metadata); } ItemStack tempStack = itemStack.copy(); tempStack.setItemDamage(metadata); /* Base cover should always be checked. */ if (effectiveSide == 6 && (!canCoverSide(TE, world, TE.xCoord, TE.yCoord, TE.zCoord, 6) || TE.hasAttribute(TE.ATTR_COVER[6]))) { effectiveSide = side; } if (canCoverSide(TE, world, TE.xCoord, TE.yCoord, TE.zCoord, effectiveSide) && !TE.hasAttribute(TE.ATTR_COVER[effectiveSide])) { TE.addAttribute(TE.ATTR_COVER[effectiveSide], tempStack); actionResult.setAltered().decInventory().setSoundSource(itemStack); } } else if (entityPlayer.isSneaking()) { if (FeatureRegistry.enableIllumination && BlockProperties.isIlluminator(itemStack)) { if (!TE.hasAttribute(TE.ATTR_ILLUMINATOR)) { TE.addAttribute(TE.ATTR_ILLUMINATOR, itemStack); actionResult.setAltered().decInventory().setSoundSource(itemStack); } } else if (FeatureRegistry.enableOverlays && BlockProperties.isOverlay(itemStack)) { if (!TE.hasAttribute(TE.ATTR_OVERLAY[effectiveSide]) && (effectiveSide < 6 && TE.hasAttribute(TE.ATTR_COVER[effectiveSide]) || effectiveSide == 6)) { TE.addAttribute(TE.ATTR_OVERLAY[effectiveSide], itemStack); actionResult.setAltered().decInventory().setSoundSource(itemStack); } } else if (FeatureRegistry.enableDyeColors && BlockProperties.isDye(itemStack, false)) { if (!TE.hasAttribute(TE.ATTR_DYE[effectiveSide])) { TE.addAttribute(TE.ATTR_DYE[effectiveSide], itemStack); actionResult.setAltered().decInventory().setSoundSource(itemStack); } } } } } } if (!actionResult.altered) { // If no prior or regular event occurred, try a post event postOnBlockActivated(TE, entityPlayer, side, hitX, hitY, hitZ, actionResult); } else { if (actionResult.itemStack == null) { actionResult.setSoundSource(BlockProperties.getCover(TE, 6)); } damageItemWithChance(world, entityPlayer); onNeighborBlockChange(world, TE.xCoord, TE.yCoord, TE.zCoord, this); world.notifyBlocksOfNeighborChange(TE.xCoord, TE.yCoord, TE.zCoord, this); } if (actionResult.playSound) { BlockProperties.playBlockSound(TE.getWorldObj(), actionResult.itemStack, TE.xCoord, TE.yCoord, TE.zCoord, false); } if (actionResult.decInv) { EntityLivingUtil.decrementCurrentSlot(entityPlayer); } return actionResult.altered; } /** * Cycles through chisel patterns. */ public boolean onChiselClick(TEBase TE, int side, boolean leftClick) { String design = TE.getChiselDesign(side); String designAdj = ""; if (design.equals("")) { World world = TE.getWorldObj(); /* Match pattern with adjacent pattern if possible. */ TEBase[] TE_list = getAdjacentTileEntities(world, TE.xCoord, TE.yCoord, TE.zCoord); for (TEBase TE_current : TE_list) { if (TE_current != null) { TE_current.getBlockType(); if (TE_current.hasChiselDesign(side)) { design = TE_current.getChiselDesign(side); designAdj = design; } } } } if (designAdj.equals("")) { design = leftClick ? DesignHandler.getPrev("chisel", design) : DesignHandler.getNext("chisel", design); } if (!design.equals("")) { TE.setChiselDesign(side, design); } return true; } @Override /** * Lets the block know when one of its neighbor changes. Doesn't know which neighbor changed (coordinates passed are * their own) Args: x, y, z, neighbor blockID */ public void onNeighborBlockChange(World world, int x, int y, int z, Block block) { /* This will check for and eject side covers that are obstructed. */ if (!world.isRemote) { TEBase TE = getTileEntity(world, x, y, z); if (TE != null) { for (int side = 0; side < 6; ++side) { if (TE.hasAttribute(TE.ATTR_COVER[side])) { if (!canCoverSide(TE, world, x, y, z, side)) { TE.removeAttributes(side); continue; } ForgeDirection dir = ForgeDirection.getOrientation(side); if (world.isSideSolid(x + dir.offsetX, y + dir.offsetY, z + dir.offsetZ, dir.getOpposite()) && isSideSolid(world, x, y, z, dir)) { TE.removeAttributes(side); } } } } } } @Override /** * Returns true if the block is emitting indirect/weak redstone power on the specified side. If isBlockNormalCube * returns true, standard redstone propagation rules will apply instead and this will not be called. Args: World, X, * Y, Z, side. Note that the side is reversed - eg it is 1 (up) when checking the bottom of the block. */ public int isProvidingWeakPower(IBlockAccess blockAccess, int x, int y, int z, int side) { TEBase TE = getTileEntity(blockAccess, x, y, z); int power = 0; /* Indirect power is provided by any cover. */ if (TE != null) { for (int idx = 0; idx < 7; ++idx) { if (TE.hasAttribute(TE.ATTR_COVER[idx])) { Block block = BlockProperties.toBlock(BlockProperties.getCover(TE, idx)); int tempPower = block.isProvidingWeakPower(blockAccess, x, y, z, side); if (tempPower > power) { power = tempPower; } } } } return power; } @Override /** * Returns true if the block is emitting direct/strong redstone power on the specified side. Args: World, X, Y, Z, * side. Note that the side is reversed - eg it is 1 (up) when checking the bottom of the block. */ public int isProvidingStrongPower(IBlockAccess blockAccess, int x, int y, int z, int side) { TEBase TE = getTileEntity(blockAccess, x, y, z); int power = 0; /* Strong power is provided by the base cover, or a side cover if one exists. */ int effectiveSide = ForgeDirection.OPPOSITES[side]; if (BlockProperties.hasAttribute(TE, TE.ATTR_COVER[effectiveSide])) { Block block = BlockProperties.toBlock(BlockProperties.getCover(TE, effectiveSide)); int tempPower = block.isProvidingWeakPower(blockAccess, x, y, z, side); if (tempPower > power) { power = tempPower; } } else if (BlockProperties.hasAttribute(TE, TE.ATTR_COVER[6])) { Block block = BlockProperties.toBlock(BlockProperties.getCover(TE, 6)); int tempPower = block.isProvidingWeakPower(blockAccess, x, y, z, side); if (tempPower > power) { power = tempPower; } } return power; } @Override @SideOnly(Side.CLIENT) /** * Spawn a digging particle effect in the world, this is a wrapper * around EffectRenderer.addBlockHitEffects to allow the block more * control over the particles. Useful when you have entirely different * texture sheets for different sides/locations in the world. * * @param world The current world * @param target The target the player is looking at {x/y/z/side/sub} * @param effectRenderer A reference to the current effect renderer. * @return True to prevent vanilla digging particles form spawning. */ public boolean addHitEffects(World world, MovingObjectPosition target, EffectRenderer effectRenderer) { TEBase TE = getTileEntity(world, target.blockX, target.blockY, target.blockZ); if (TE != null) { int effectiveSide = TE.hasAttribute(TE.ATTR_COVER[target.sideHit]) ? target.sideHit : 6; ItemStack itemStack = BlockProperties.getCover(TE, effectiveSide); if (BlockProperties.hasAttribute(TE, TE.ATTR_OVERLAY[effectiveSide])) { Overlay overlay = OverlayHandler.getOverlayType(TE.getAttribute(TE.ATTR_OVERLAY[effectiveSide])); if (OverlayHandler.coversFullSide(overlay, target.sideHit)) { itemStack = overlay.getItemStack(); } } Block block = BlockProperties.toBlock(itemStack); double xOffset = target.blockX + world.rand.nextDouble() * (block.getBlockBoundsMaxX() - block.getBlockBoundsMinX() - 0.1F * 2.0F) + 0.1F + block.getBlockBoundsMinX(); double yOffset = target.blockY + world.rand.nextDouble() * (block.getBlockBoundsMaxY() - block.getBlockBoundsMinY() - 0.1F * 2.0F) + 0.1F + block.getBlockBoundsMinY(); double zOffset = target.blockZ + world.rand.nextDouble() * (block.getBlockBoundsMaxZ() - block.getBlockBoundsMinZ() - 0.1F * 2.0F) + 0.1F + block.getBlockBoundsMinZ(); switch (target.sideHit) { case 0: yOffset = target.blockY + block.getBlockBoundsMinY() - 0.1D; break; case 1: yOffset = target.blockY + block.getBlockBoundsMaxY() + 0.1D; break; case 2: zOffset = target.blockZ + block.getBlockBoundsMinZ() - 0.1D; break; case 3: zOffset = target.blockZ + block.getBlockBoundsMaxZ() + 0.1D; break; case 4: xOffset = target.blockX + block.getBlockBoundsMinX() - 0.1D; break; case 5: xOffset = target.blockX + block.getBlockBoundsMaxX() + 0.1D; break; } ParticleHelper.addHitEffect(TE, target, xOffset, yOffset, zOffset, itemStack, effectRenderer); return true; } return super.addHitEffects(world, target, effectRenderer); } @Override @SideOnly(Side.CLIENT) /** * Renders block destruction effects. * This is controlled to prevent block destroy effects if left-clicked with a Carpenter's Hammer while player is in creative mode. * * Returns false to display effects. True suppresses them (backwards). */ public boolean addDestroyEffects(World world, int x, int y, int z, int metadata, EffectRenderer effectRenderer) { /* * We don't have the ability to accurately determine the entity that is * hitting the block. So, instead we're guessing based on who is * closest. This should be adequate most of the time. */ TEBase TE = getTileEntity(world, x, y, z); if (TE != null) { EntityPlayer entityPlayer = world.getClosestPlayer(x, y, z, 6.5F); if (entityPlayer != null) { if (!suppressDestroyBlock(entityPlayer)) { ParticleHelper.addDestroyEffect(world, x, y, z, BlockProperties.getCover(TE, 6), effectRenderer); } else { return true; } } } return false; } @Override /** * Returns light value based on cover or side covers. */ public int getLightValue(IBlockAccess blockAccess, int x, int y, int z) { TEBase TE = getSimpleTileEntity(blockAccess, x, y, z); if (TE != null) { return TE.getLightValue(); } return 0; } @Override /** * Returns the block hardness at a location. Args: world, x, y, z */ public float getBlockHardness(World world, int x, int y, int z) { TEBase TE = getTileEntity(world, x, y, z); if (BlockProperties.hasAttribute(TE, TE.ATTR_COVER[6])) { ItemStack is = BlockProperties.getCover(TE, 6); Block b = BlockProperties.toBlock(is); if (b instanceof BlockCoverable) { return blockHardness; } return b instanceof IWrappableBlock ? ((IWrappableBlock)b).getHardness(world, x, y, z, b, is.getItemDamage()) : b.getBlockHardness(world, x, y, z); } return blockHardness; } @Override /** * Chance that fire will spread and consume this block. */ public int getFlammability(IBlockAccess blockAccess, int x, int y, int z, ForgeDirection side) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (TE != null) { ItemStack is = BlockProperties.getFeatureSensitiveSideItemStack(TE, side); Block b = BlockProperties.toBlock(is); return b instanceof IWrappableBlock ? ((IWrappableBlock)b).getFlammability(blockAccess, x, y, z, side, b, is.getItemDamage()) : Blocks.fire.getFlammability(b); } return super.getFlammability(blockAccess, x, y, z, side); } @Override /** * Called when fire is updating on a neighbor block. */ public int getFireSpreadSpeed(IBlockAccess blockAccess, int x, int y, int z, ForgeDirection side) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (TE != null) { ItemStack is = BlockProperties.getFeatureSensitiveSideItemStack(TE, side); Block b = BlockProperties.toBlock(is); return b instanceof IWrappableBlock ? ((IWrappableBlock)b).getFireSpread(blockAccess, x, y, z, side, b, is.getItemDamage()) : Blocks.fire.getEncouragement(b); } return super.getFireSpreadSpeed(blockAccess, x, y, z, side); } @Override /** * Currently only called by fire when it is on top of this block. * Returning true will prevent the fire from naturally dying during updating. * Also prevents fire from dying from rain. */ public boolean isFireSource(World world, int x, int y, int z, ForgeDirection side) { TEBase TE = getTileEntity(world, x, y, z); if (TE != null) { ItemStack is = BlockProperties.getFeatureSensitiveSideItemStack(TE, side); Block b = BlockProperties.toBlock(is); if (b instanceof BlockCoverable) { return false; } return b instanceof IWrappableBlock ? ((IWrappableBlock)b).sustainsFire(world, x, y, z, side, b, is.getItemDamage()) : b.isFireSource(world, x, y, z, side); } return false; } @Override /** * Location sensitive version of getExplosionRestance */ public float getExplosionResistance(Entity entity, World world, int x, int y, int z, double explosionX, double explosionY, double explosionZ) { TEBase TE = getTileEntity(world, x, y, z); if (BlockProperties.hasAttribute(TE, TE.ATTR_COVER[6])) { ItemStack is = BlockProperties.getCover(TE, 6); Block b = BlockProperties.toBlock(is); if (b instanceof BlockCoverable) { return this.getExplosionResistance(entity); } return b instanceof IWrappableBlock ? ((IWrappableBlock)b).getBlastResistance(entity, world, x, y, z, explosionX, explosionY, explosionZ, b, is.getItemDamage()) : b.getExplosionResistance(entity, world, x, y, z, explosionX, explosionY, explosionZ); } return this.getExplosionResistance(entity); } @Override /** * Returns whether block is wood */ public boolean isWood(IBlockAccess blockAccess, int x, int y, int z) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (BlockProperties.hasAttribute(TE, TE.ATTR_COVER[6])) { ItemStack is = BlockProperties.getCover(TE, 6); Block b = BlockProperties.toBlock(is); if (b instanceof BlockCoverable) { return true; } return b instanceof IWrappableBlock ? ((IWrappableBlock)b).isLog(blockAccess, x, y, z, b, is.getItemDamage()) : b.isWood(blockAccess, x, y, z); } return super.isWood(blockAccess, x, y, z); } /** * Determines if this block is can be destroyed by the specified entities normal behavior. */ @Override public boolean canEntityDestroy(IBlockAccess blockAccess, int x, int y, int z, Entity entity) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (BlockProperties.hasAttribute(TE, TE.ATTR_COVER[6])) { Block block = BlockProperties.toBlock(BlockProperties.getCover(TE, 6)); if (entity instanceof EntityWither) { return !block.equals(Blocks.bedrock) && !block.equals(Blocks.end_portal) && !block.equals(Blocks.end_portal_frame) && !block.equals(Blocks.command_block); } else if (entity instanceof EntityDragon) { return !block.equals(Blocks.obsidian) && !block.equals(Blocks.end_stone) && !block.equals(Blocks.bedrock); } } return super.canEntityDestroy(blockAccess, x, y, z, entity); } @Override /** * Triggered whenever an entity collides with this block (enters into the block). Args: world, x, y, z, entity */ public void onEntityCollidedWithBlock(World world, int x, int y, int z, Entity entity) { TEBase TE = getTileEntity(world, x, y, z); if (BlockProperties.hasAttribute(TE, TE.ATTR_COVER[6])) { BlockProperties.toBlock(BlockProperties.getCover(TE, 6)).onEntityCollidedWithBlock(world, x, y, z, entity); } } @Override /** * Spawns EntityItem in the world for the given ItemStack if the world is not remote. */ protected void dropBlockAsItem(World world, int x, int y, int z, ItemStack itemStack) { // Clear metadata for Carpenter's blocks Block block = BlockProperties.toBlock(itemStack); if (block instanceof BlockCoverable) { itemStack.setItemDamage(0); } super.dropBlockAsItem(world, x, y, z, itemStack); } @Override /** * Called when the player destroys a block with an item that can harvest it. (i, j, k) are the coordinates of the * block and l is the block's subtype/damage. */ public void harvestBlock(World p_149636_1_, EntityPlayer p_149636_2_, int p_149636_3_, int p_149636_4_, int p_149636_5_, int p_149636_6_) {} /** * Indicates whether block destruction should be suppressed when block is clicked. * Will return true if player is holding a Carpenter's tool in creative mode. */ protected boolean suppressDestroyBlock(EntityPlayer entityPlayer) { if (entityPlayer == null) { return false; } ItemStack itemStack = entityPlayer.getHeldItem(); if (itemStack != null) { Item item = itemStack.getItem(); return entityPlayer.capabilities.isCreativeMode && item != null && (item instanceof ICarpentersHammer || item instanceof ICarpentersChisel); } return false; } /** * Drops block as {@link ItemStack} and notifies relevant systems of * block removal. Block attributes will drop later in destruction. * <p> * This is usually called when a {@link #onNeighborBlockChange(World, int, int, int, Block) neighbor changes}. * * @param world the {@link World} * @param x the x coordinate * @param y the y coordinate * @param z the z coordinate * @param dropBlock whether block {@link ItemStack} is dropped */ protected void destroyBlock(World world, int x, int y, int z, boolean dropBlock) { // Drop attributes int metadata = dropBlock ? 0 : METADATA_DROP_ATTR_ONLY; ArrayList<ItemStack> items = getDrops(world, x, y, z, metadata, 0); for (ItemStack item : items) { dropBlockAsItem(world, x, y, z, item); } world.setBlockToAir(x, y, z); } @Override /** * Called when a player removes a block. This is responsible for * actually destroying the block, and the block is intact at time of call. * This is called regardless of whether the player can harvest the block or * not. * * Return true if the block is actually destroyed. * * Note: When used in multiplayer, this is called on both client and * server sides! * * @param world The current world * @param player The player damaging the block, may be null * @param x X Position * @param y Y position * @param z Z position * @param willHarvest True if Block.harvestBlock will be called after this, if the return in true. * Can be useful to delay the destruction of tile entities till after harvestBlock * @return True if the block is actually destroyed. */ public boolean removedByPlayer(World world, EntityPlayer entityPlayer, int x, int y, int z, boolean willHarvest) { if (world.isRemote) { return super.removedByPlayer(world, entityPlayer, x, y, z, willHarvest); } // Grab drops while tile entity exists (before calling super) int metadata = entityPlayer != null && entityPlayer.capabilities.isCreativeMode ? METADATA_DROP_ATTR_ONLY : 0; ArrayList<ItemStack> items = getDrops(world, x, y, z, metadata, 0); // Drop attributes if block destroyed, and no Carpenter's Tool is held by entity if (!suppressDestroyBlock(entityPlayer) && super.removedByPlayer(world, entityPlayer, x, y, z, willHarvest)) { for (ItemStack item : items) { dropBlockAsItem(world, x, y, z, item); } return true; } return false; } /** * This returns a complete list of items dropped from this block. * * @param world The current world * @param x X Position * @param y Y Position * @param z Z Position * @param metadata Current metadata * @param fortune Breakers fortune level * @return A ArrayList containing all items this block drops */ @Override public ArrayList<ItemStack> getDrops(World world, int x, int y, int z, int metadata, int fortune) { ArrayList<ItemStack> ret = super.getDrops(world, x, y, z, metadata, fortune); // Add block item drop TEBase TE = getSimpleTileEntity(world, x, y, z); if (metadata == METADATA_DROP_ATTR_ONLY) { ret.clear(); // Remove block instance from drop list } if (TE != null) { for (int idx = 0; idx < 7; ++idx) { if (TE.hasAttribute(TE.ATTR_COVER[idx])) { ret.add(TE.getAttributeForDrop(TE.ATTR_COVER[idx])); } if (TE.hasAttribute(TE.ATTR_OVERLAY[idx])) { ret.add(TE.getAttributeForDrop(TE.ATTR_OVERLAY[idx])); } if (TE.hasAttribute(TE.ATTR_DYE[idx])) { ret.add(TE.getAttributeForDrop(TE.ATTR_DYE[idx])); } } if (TE.hasAttribute(TE.ATTR_ILLUMINATOR)) { ret.add(TE.getAttributeForDrop(TE.ATTR_ILLUMINATOR)); } } return ret; } @Override @SideOnly(Side.CLIENT) /** * A randomly called display update to be able to add particles or other items for display */ public void randomDisplayTick(World world, int x, int y, int z, Random random) { TEBase TE = getTileEntity(world, x, y, z); if (TE != null) { if (TE.hasAttribute(TE.ATTR_COVER[6])) { BlockProperties.toBlock(BlockProperties.getCover(TE, 6)).randomDisplayTick(world, x, y, z, random); } if (TE.hasAttribute(TE.ATTR_OVERLAY[6])) { if (OverlayHandler.getOverlayType(TE.getAttribute(TE.ATTR_OVERLAY[6])).equals(Overlay.MYCELIUM)) { Blocks.mycelium.randomDisplayTick(world, x, y, z, random); } } } } /** * Determines if this block can support the passed in plant, allowing it to be planted and grow. * Some examples: * Reeds check if its a reed, or if its sand/dirt/grass and adjacent to water * Cacti checks if its a cacti, or if its sand * Nether types check for soul sand * Crops check for tilled soil * Caves check if it's a solid surface * Plains check if its grass or dirt * Water check if its still water * * @param blockAccess The current world * @param x X Position * @param y Y Position * @param z Z position * @param side The direction relative to the given position the plant wants to be, typically its UP * @param plantable The plant that wants to check * @return True to allow the plant to be planted/stay. */ @Override public boolean canSustainPlant(IBlockAccess blockAccess, int x, int y, int z, ForgeDirection side, IPlantable plantable) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (TE != null) { /* If side is not solid, it can't sustain a plant. */ if (!isSideSolid(blockAccess, x, y, z, side)) { return false; } /* * Add base block, top block, and both of their associated * overlays to judge whether plants can be supported on block. */ List<Block> blocks = new ArrayList<Block>(); for (int side1 = 1; side1 < 7; side1 += 5) { if (TE.hasAttribute(TE.ATTR_COVER[side1])) { blocks.add(BlockProperties.toBlock(BlockProperties.getCover(TE, side1))); } if (TE.hasAttribute(TE.ATTR_OVERLAY[side1])) { blocks.add(BlockProperties.toBlock(OverlayHandler.getOverlayType(TE.getAttribute(TE.ATTR_OVERLAY[side1])).getItemStack())); } } /* Add types using cover material */ Material material = BlockProperties.toBlock(BlockProperties.getCover(TE, 6)).getMaterial(); if (material.equals(Material.grass)) { blocks.add(Blocks.grass); } else if (material.equals(Material.ground)) { blocks.add(Blocks.dirt); } else if (material.equals(Material.sand)) { blocks.add(Blocks.sand); } switch (plantable.getPlantType(blockAccess, x, y + 1, z)) { case Desert: return blocks.contains(Blocks.sand); case Nether: return blocks.contains(Blocks.soul_sand); case Plains: return blocks.contains(Blocks.grass) || blocks.contains(Blocks.dirt); case Beach: boolean isBeach = blocks.contains(Blocks.grass) || blocks.contains(Blocks.dirt) || blocks.contains(Blocks.sand); boolean hasWater = blockAccess.getBlock(x - 1, y, z ).getMaterial() == Material.water || blockAccess.getBlock(x + 1, y, z ).getMaterial() == Material.water || blockAccess.getBlock(x, y, z - 1).getMaterial() == Material.water || blockAccess.getBlock(x, y, z + 1).getMaterial() == Material.water; return isBeach && hasWater; default: break; } } return super.canSustainPlant(blockAccess, x, y, z, side, plantable); } /** * Returns whether this block is considered solid. */ protected boolean isBlockSolid(IBlockAccess blockAccess, int x, int y, int z) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (TE != null) { return !TE.hasAttribute(TE.ATTR_COVER[6]) || BlockProperties.toBlock(BlockProperties.getCover(TE, 6)).isOpaqueCube(); } else { return false; } } @Override /** * Called when the block is placed in the world. */ public void onBlockPlacedBy(World world, int x, int y, int z, EntityLivingBase entityLiving, ItemStack itemStack) { if (!world.isRemote) { TEBase TE = getTileEntity(world, x, y, z); if (TE != null) { TE.setOwner(new ProtectedObject((EntityPlayer)entityLiving)); } } } @Override /** * Gets the hardness of block at the given coordinates in the given world, relative to the ability of the given * EntityPlayer. */ public float getPlayerRelativeBlockHardness(EntityPlayer entityPlayer, World world, int x, int y, int z) { /* Don't damage block if holding Carpenter's tool. */ ItemStack itemStack = entityPlayer.getHeldItem(); if (itemStack != null) { Item item = itemStack.getItem(); if (item instanceof ICarpentersHammer || item instanceof ICarpentersChisel) { return -1; } } /* Return block hardness of cover. */ TEBase TE = getTileEntity(world, x, y, z); if (TE != null) { return ForgeHooks.blockStrength(BlockProperties.toBlock(BlockProperties.getCover(TE, 6)), entityPlayer, world, x, y, z); } else { return super.getPlayerRelativeBlockHardness(entityPlayer, world, x, y, z); } } @SideOnly(Side.CLIENT) @Override public int colorMultiplier(IBlockAccess blockAccess, int x, int y, int z) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (TE != null) { ItemStack itemStack = BlockProperties.getCover(TE, 6); Block block = BlockProperties.toBlock(itemStack); if (!(block instanceof BlockCoverable)) { return block instanceof IWrappableBlock ? ((IWrappableBlock)block).getColorMultiplier(blockAccess, x, y, z, block, itemStack.getItemDamage()) : block.colorMultiplier(blockAccess, x, y, z); } } return super.colorMultiplier(blockAccess, x, y, z); } @Override @SideOnly(Side.CLIENT) /** * Returns true if the given side of this block type should be rendered, if the adjacent block is at the given * coordinates. Args: world, x, y, z, side */ public boolean shouldSideBeRendered(IBlockAccess blockAccess, int x, int y, int z, int side) { // Side checks in out-of-range areas will crash if (y > 0 && y < blockAccess.getHeight()) { TEBase TE = getTileEntity(blockAccess, x, y, z); if (TE != null) { ForgeDirection side_src = ForgeDirection.getOrientation(side); ForgeDirection side_adj = side_src.getOpposite(); TEBase TE_adj = (TEBase) blockAccess.getTileEntity(x, y, z); TEBase TE_src = (TEBase) blockAccess.getTileEntity(x + side_adj.offsetX, y + side_adj.offsetY, z + side_adj.offsetZ); if (TE_adj.getBlockType().isSideSolid(blockAccess, x, y, z, side_adj) == TE_src.getBlockType().isSideSolid(blockAccess, x + side_adj.offsetX, y + side_adj.offsetY, z + side_adj.offsetZ, ForgeDirection.getOrientation(side))) { if (shareFaces(TE_adj, TE_src, side_adj, side_src)) { Block block_adj = BlockProperties.toBlock(BlockProperties.getCover(TE_adj, 6)); Block block_src = BlockProperties.toBlock(BlockProperties.getCover(TE_src, 6)); if (!TE_adj.hasAttribute(TE.ATTR_COVER[6])) { return TE_src.hasAttribute(TE.ATTR_COVER[6]); } else { if (!TE_src.hasAttribute(TE.ATTR_COVER[6]) && block_adj.getRenderBlockPass() == 0) { return !block_adj.isOpaqueCube(); } else if (TE_src.hasAttribute(TE.ATTR_COVER[6]) && block_src.isOpaqueCube() == block_adj.isOpaqueCube() && block_src.getRenderBlockPass() == block_adj.getRenderBlockPass()) { return false; } else { return true; } } } } } } return super.shouldSideBeRendered(blockAccess, x, y, z, side); } @Override /** * Determines if this block should render in this pass. */ public boolean canRenderInPass(int pass) { ForgeHooksClient.setRenderPass(pass); return true; } @Override @SideOnly(Side.CLIENT) /** * Returns which pass this block be rendered on. 0 for solids and 1 for alpha. */ public int getRenderBlockPass() { /* * Alpha properties of block or cover depend on this returning a value * of 1, so it's the default value. However, when rendering in player * hand we'll encounter sorting artifacts, and thus need to enforce * opaque rendering, or 0. */ if (ForgeHooksClient.getWorldRenderPass() < 0) { return 0; } else { return 1; } } /** * Returns whether two blocks share faces. * Primarily for slopes, stairs and slabs. */ protected boolean shareFaces(TEBase TE_adj, TEBase TE_src, ForgeDirection side_adj, ForgeDirection side_src) { return TE_adj.getBlockType().isSideSolid(TE_adj.getWorldObj(), TE_adj.xCoord, TE_adj.yCoord, TE_adj.zCoord, side_adj) && TE_src.getBlockType().isSideSolid(TE_src.getWorldObj(), TE_src.xCoord, TE_src.yCoord, TE_src.zCoord, side_src); } @Override /** * Is this block (a) opaque and (b) a full 1m cube? This determines whether or not to render the shared face of two * adjacent blocks and also whether the player can attach torches, redstone wire, etc to this block. */ public boolean isOpaqueCube() { if (FMLCommonHandler.instance().getEffectiveSide().isServer()) { return false; } if (FeatureRegistry.enableRoutableFluids) { // Server condition may fail, so don't throw error if performing server-side try { Class<?> clazz = RoutableFluidsHelper.getCallerClass(); if (clazz != null) { for (Class clazz1 : RoutableFluidsHelper.liquidClasses) { if (clazz.isAssignableFrom(clazz1)) { return true; } } } } catch (Exception e) {} } return false; } @Override /** * If this block doesn't render as an ordinary block it will return False (examples: signs, buttons, stairs, etc) */ public boolean renderAsNormalBlock() { return false; } /** * Should block use the brightest neighbor light value as its own */ @Override public boolean getUseNeighborBrightness() { return true; } @Override /** * Called whenever the block is added into the world. Args: world, x, y, z */ public void onBlockAdded(World world, int x, int y, int z) { world.setTileEntity(x, y, z, createNewTileEntity(world, 0)); } @Override public TileEntity createNewTileEntity(World world, int metadata) { return new TEBase(); } @Override public boolean hasTileEntity(int metadata) { return true; } /** * This method is configured on as as-needed basis. * It's calling order is not guaranteed. */ protected void preOnBlockClicked(TEBase TE, World world, int x, int y, int z, EntityPlayer entityPlayer, ActionResult actionResult) {} /** * Called before cover or decoration checks are performed. */ protected void preOnBlockActivated(TEBase TE, EntityPlayer entityPlayer, int side, float hitX, float hitY, float hitZ, ActionResult actionResult) {} /** * Called if cover and decoration checks have been performed but * returned no changes. */ protected void postOnBlockActivated(TEBase TE, EntityPlayer entityPlayer, int side, float hitX, float hitY, float hitZ, ActionResult actionResult) {} protected boolean onHammerLeftClick(TEBase TE, EntityPlayer entityPlayer) { return false; } protected boolean onHammerRightClick(TEBase TE, EntityPlayer entityPlayer) { return false; } protected void damageItemWithChance(World world, EntityPlayer entityPlayer) { Item item = entityPlayer.getCurrentEquippedItem().getItem(); if (item instanceof ICarpentersHammer) { ((ICarpentersHammer) item).onHammerUse(world, entityPlayer); } else if (item instanceof ICarpentersChisel) { ((ICarpentersChisel) item).onChiselUse(world, entityPlayer); } } /** * Returns whether side of block supports a cover. */ protected boolean canCoverSide(TEBase TE, World world, int x, int y, int z, int side) { return side == 6; } /** * Allows a tile entity called during block activation to be changed before * altering attributes like cover, dye, overlay, etc. * <p> * Primarily offered for the garage door, when open, to swap the top piece * with the bottom piece for consistency. * * @param TE the originating {@link TEBase} * @return a swapped in {@link TEBase}, or the passed in {@link TEBase} */ protected TEBase getTileEntityForBlockActivation(TEBase TE) { return TE; } }