@Override
  /** Updates the blocks bounds based on its current state. Args: world, x, y, z */
  public void setBlockBoundsBasedOnState(IBlockAccess blockAccess, int x, int y, int z) {
    if (!rayTracing) {

      TEBase TE = getTileEntity(blockAccess, x, y, z);

      if (TE != null) {

        int slopeID = TE.getData();
        Slope slope = Slope.slopesList[slopeID];

        switch (slope.getPrimaryType()) {
          case PRISM:
          case PRISM_1P:
          case PRISM_2P:
          case PRISM_3P:
          case PRISM_4P:
            if (slope.isPositive) {
              setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.5F, 1.0F);
            } else {
              setBlockBounds(0.0F, 0.5F, 0.0F, 1.0F, 1.0F, 1.0F);
            }
            break;
          default:
            setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
            break;
        }
      }
    }
  }
  @Override
  /** Returns whether sides share faces based on sloping property and face shape. */
  protected boolean shareFaces(
      TEBase TE_adj, TEBase TE_src, ForgeDirection side_adj, ForgeDirection side_src) {
    if (TE_adj.getBlockType() == this) {

      Slope slope_src = Slope.slopesList[TE_src.getData()];
      Slope slope_adj = Slope.slopesList[TE_adj.getData()];

      if (!slope_adj.hasSide(side_adj)) {
        return false;
      } else if (slope_src.getFaceBias(side_src) == slope_adj.getFaceBias(side_adj)) {
        return true;
      } else {
        return false;
      }
    }

    return super.shareFaces(TE_adj, TE_src, side_adj, side_src);
  }
  @Override
  /** Alters block type. */
  protected boolean onHammerRightClick(TEBase TE, EntityPlayer entityPlayer) {
    int slopeID = TE.getData();
    Slope slope = Slope.slopesList[slopeID];

    /* Transform slope to next type. */
    slopeID = slope.slopeType.onHammerRightClick(slope, slopeID);

    TE.setData(slopeID);

    return true;
  }
  @Override
  /** Alters block direction. */
  protected boolean onHammerLeftClick(TEBase TE, EntityPlayer entityPlayer) {
    int slopeID = TE.getData();
    Slope slope = Slope.slopesList[slopeID];

    /* Cycle between slope types based on current slope. */
    slopeID = slope.slopeType.onHammerLeftClick(slope, slopeID);

    TE.setData(slopeID);

    return true;
  }
  @Override
  /** Checks if the block is a solid face on the given side, used by placement logic. */
  public boolean isSideSolid(IBlockAccess blockAccess, int x, int y, int z, ForgeDirection side) {
    TEBase TE = getTileEntity(blockAccess, x, y, z);

    if (TE != null) {
      if (isBlockSolid(blockAccess, x, y, z)) {
        return Slope.slopesList[TE.getData()].isFaceFull(side);
      }
    }

    return false;
  }
  @Override
  /**
   * Ray traces through the blocks collision from start vector to end vector returning a ray trace
   * hit. Args: world, x, y, z, startVec, endVec
   */
  public MovingObjectPosition collisionRayTrace(
      World world, int x, int y, int z, Vec3 startVec, Vec3 endVec) {
    TEBase TE = getTileEntity(world, x, y, z);
    MovingObjectPosition finalTrace = null;

    if (TE != null) {

      Slope slope = Slope.slopesList[TE.getData()];
      SlopeUtil slopeUtil = new SlopeUtil();

      int numPasses = slopeUtil.getNumPasses(slope);
      int precision = slopeUtil.getNumBoxesPerPass(slope);

      rayTracing = true;

      /* Determine if ray trace is a hit on slope. */
      for (int pass = 0; pass < numPasses; ++pass) {
        for (int slice = 0; slice < precision && finalTrace == null; ++slice) {
          float[] box = slopeUtil.genBounds(slope, slice, precision, pass);

          if (box != null) {
            setBlockBounds(box[0], box[1], box[2], box[3], box[4], box[5]);
            finalTrace = super.collisionRayTrace(world, x, y, z, startVec, endVec);
          }
        }
        if (slope.type.equals(Type.OBLIQUE_EXT)) {
          --precision;
        }
      }

      rayTracing = false;

      /* Determine true face hit since sloped faces are two or more shared faces. */

      if (finalTrace != null) {
        setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
        finalTrace = super.collisionRayTrace(world, x, y, z, startVec, endVec);
      }
    }

    return finalTrace;
  }
  @Override
  /**
   * Adds all intersecting collision boxes to a list. (Be sure to only add boxes to the list if they
   * intersect the mask.) Parameters: World, X, Y, Z, mask, list, colliding entity
   */
  public void addCollisionBoxesToList(
      World world, int x, int y, int z, AxisAlignedBB axisAlignedBB, List list, Entity entity) {
    TEBase TE = getTileEntity(world, x, y, z);

    if (TE != null) {

      AxisAlignedBB box = null;

      Slope slope = Slope.slopesList[TE.getData()];
      SlopeUtil slopeUtil = new SlopeUtil();

      int precision = slopeUtil.getNumBoxesPerPass(slope);
      int numPasses = slopeUtil.getNumPasses(slope);

      for (int pass = 0; pass < numPasses; ++pass) {

        for (int slice = 0; slice < precision; ++slice) {
          float[] dim = slopeUtil.genBounds(slope, slice, precision, pass);

          if (dim != null) {
            box =
                AxisAlignedBB.getBoundingBox(
                    x + dim[0], y + dim[1], z + dim[2], x + dim[3], y + dim[4], z + dim[5]);
          }

          if (box != null && axisAlignedBB.intersectsWith(box)) {
            list.add(box);
          }
        }

        if (slope.type.equals(Type.OBLIQUE_EXT)) {
          --precision;
        }
      }
    }
  }
  @Override
  public boolean rotateBlock(World world, int x, int y, int z, ForgeDirection axis) {
    // to correctly support archimedes' ships mod:
    // if Axis is DOWN, block rotates to the left, north -> west -> south -> east
    // if Axis is UP, block rotates to the right:  north -> east -> south -> west

    TileEntity tile = world.getTileEntity(x, y, z);
    if (tile != null && tile instanceof TEBase) {
      TEBase cbTile = (TEBase) tile;
      int data = cbTile.getData();
      int dataAngle = data % 4;
      switch (axis) {
        case UP:
          {
            switch (dataAngle) {
              case 0:
                {
                  cbTile.setData(data + 3);
                  break;
                }
              case 1:
                {
                  cbTile.setData(data + 1);
                  break;
                }
              case 2:
                {
                  cbTile.setData(data - 2);
                  break;
                }
              case 3:
                {
                  cbTile.setData(data - 2);
                  break;
                }
            }
            break;
          }
        case DOWN:
          {
            switch (dataAngle) {
              case 0:
                {
                  cbTile.setData(data + 2);
                  break;
                }
              case 1:
                {
                  cbTile.setData(data + 2);
                  break;
                }
              case 2:
                {
                  cbTile.setData(data - 1);
                  break;
                }
              case 3:
                {
                  cbTile.setData(data - 3);
                  break;
                }
            }
            break;
          }
        default:
          return false;
      }
      return true;
    }
    return false;
  }