private static void createSide(
      BoundingBoxMap<BoundingBoxMap<ShapeManipulator>> output,
      ForgeDirection face,
      ShapeManipulator middleClick,
      ShapeManipulator horMinClick,
      ShapeManipulator horPlusClick,
      ShapeManipulator vertMinClick,
      ShapeManipulator vertPlusClick) {
    final BlockTextureTransform transform = new BlockTextureTransform(face);

    final BoundingBoxMap<ShapeManipulator> subBoxes = BoundingBoxMap.create();

    addButton(transform, subBoxes, 1, 6, 5, 10, horPlusClick);
    addButton(transform, subBoxes, 6, 1, 10, 5, vertMinClick);
    addButton(transform, subBoxes, 11, 6, 15, 10, horMinClick);
    addButton(transform, subBoxes, 6, 11, 10, 15, vertPlusClick);

    addButton(transform, subBoxes, 6, 6, 10, 10, middleClick);

    final BlockTextureTransform.WorldCoords overboxMin =
        transform.textureCoordsToWorldVec(P, P, -SELECTION_BOX_DEPTH);
    final BlockTextureTransform.WorldCoords overboxMax =
        transform.textureCoordsToWorldVec(1 - P, 1 - P, SELECTION_BOX_DEPTH);
    output.addBox(
        AabbUtils.createAabb(
            overboxMin.x, overboxMin.y, overboxMin.z, overboxMax.x, overboxMax.y, overboxMax.z),
        subBoxes);
  }
 private static void addButton(
     BlockTextureTransform transform,
     BoundingBoxMap<ShapeManipulator> output,
     int left,
     int top,
     int right,
     int bottom,
     ShapeManipulator action) {
   final BlockTextureTransform.WorldCoords min =
       transform.textureCoordsToWorldVec(left * P, top * P, -SELECTION_BOX_DEPTH);
   final BlockTextureTransform.WorldCoords max =
       transform.textureCoordsToWorldVec(right * P, bottom * P, SELECTION_BOX_DEPTH);
   output.addBox(AabbUtils.createAabb(min.x, min.y, min.z, max.x, max.y, max.z), action);
 }
@BookDocumentation
public class BlockGuide extends OpenBlock implements ISelectionAware {

  private AxisAlignedBB selection;

  private static double P = 1.0 / 16.0;

  private static final double SELECTION_BOX_DEPTH = 0.01;

  private static void addButton(
      BlockTextureTransform transform,
      BoundingBoxMap<ShapeManipulator> output,
      int left,
      int top,
      int right,
      int bottom,
      ShapeManipulator action) {
    final BlockTextureTransform.WorldCoords min =
        transform.textureCoordsToWorldVec(left * P, top * P, -SELECTION_BOX_DEPTH);
    final BlockTextureTransform.WorldCoords max =
        transform.textureCoordsToWorldVec(right * P, bottom * P, SELECTION_BOX_DEPTH);
    output.addBox(AabbUtils.createAabb(min.x, min.y, min.z, max.x, max.y, max.z), action);
  }

  private static void createSide(
      BoundingBoxMap<BoundingBoxMap<ShapeManipulator>> output,
      ForgeDirection face,
      ShapeManipulator middleClick,
      ShapeManipulator horMinClick,
      ShapeManipulator horPlusClick,
      ShapeManipulator vertMinClick,
      ShapeManipulator vertPlusClick) {
    final BlockTextureTransform transform = new BlockTextureTransform(face);

    final BoundingBoxMap<ShapeManipulator> subBoxes = BoundingBoxMap.create();

    addButton(transform, subBoxes, 1, 6, 5, 10, horPlusClick);
    addButton(transform, subBoxes, 6, 1, 10, 5, vertMinClick);
    addButton(transform, subBoxes, 11, 6, 15, 10, horMinClick);
    addButton(transform, subBoxes, 6, 11, 10, 15, vertPlusClick);

    addButton(transform, subBoxes, 6, 6, 10, 10, middleClick);

    final BlockTextureTransform.WorldCoords overboxMin =
        transform.textureCoordsToWorldVec(P, P, -SELECTION_BOX_DEPTH);
    final BlockTextureTransform.WorldCoords overboxMax =
        transform.textureCoordsToWorldVec(1 - P, 1 - P, SELECTION_BOX_DEPTH);
    output.addBox(
        AabbUtils.createAabb(
            overboxMin.x, overboxMin.y, overboxMin.z, overboxMax.x, overboxMax.y, overboxMax.z),
        subBoxes);
  }

  private BoundingBoxMap<BoundingBoxMap<ShapeManipulator>> buttons = BoundingBoxMap.create();

  {
    createSide(buttons, ForgeDirection.NORTH, MIDDLE, X_MINUS, X_PLUS, Y_MINUS, Y_PLUS);
    createSide(buttons, ForgeDirection.SOUTH, MIDDLE, X_MINUS, X_PLUS, Y_MINUS, Y_PLUS);

    createSide(buttons, ForgeDirection.EAST, MIDDLE, Z_MINUS, Z_PLUS, Y_MINUS, Y_PLUS);
    createSide(buttons, ForgeDirection.WEST, MIDDLE, Z_MINUS, Z_PLUS, Y_MINUS, Y_PLUS);

    createSide(buttons, ForgeDirection.UP, MIDDLE, X_MINUS, X_PLUS, Z_MINUS, Z_PLUS);
    createSide(buttons, ForgeDirection.DOWN, MIDDLE, X_MINUS, X_PLUS, Z_MINUS, Z_PLUS);
  }

  public static class Icons {
    public static IIcon marker;
  }

  public BlockGuide() {
    super(Material.rock);
    setLightLevel(0.6f);
  }

  @Override
  public boolean isOpaqueCube() {
    return false;
  }

  @Override
  public boolean renderAsNormalBlock() {
    return true;
  }

  protected IIcon centerIcon;

  @Override
  public void registerBlockIcons(IIconRegister registry) {
    Icons.marker = registry.registerIcon("openblocks:guide");
    this.blockIcon = registry.registerIcon("openblocks:guide_new");
    this.centerIcon = registry.registerIcon("openblocks:guide_center_normal");
  }

  public IIcon getCenterTexture() {
    return centerIcon;
  }

  @Override
  public boolean canBeReplacedByLeaves(IBlockAccess world, int x, int y, int z) {
    return false;
  }

  @Override
  public boolean isFlammable(IBlockAccess world, int x, int y, int z, ForgeDirection face) {
    return false;
  }

  @Override
  @SideOnly(Side.CLIENT)
  public int getRenderBlockPass() {
    return 1;
  }

  @Override
  public AxisAlignedBB getSelectedBoundingBoxFromPool(World world, int x, int y, int z) {
    return selection != null ? selection : super.getSelectedBoundingBoxFromPool(world, x, y, z);
  }

  private Map.Entry<AxisAlignedBB, ShapeManipulator> findClickBox(Vec3 pos) {
    final Entry<AxisAlignedBB, BoundingBoxMap<ShapeManipulator>> sideBox =
        buttons.findEntryContainingPoint(pos);
    if (sideBox != null) {
      final Entry<AxisAlignedBB, ShapeManipulator> subSideBox =
          sideBox.getValue().findEntryContainingPoint(pos);
      if (subSideBox != null) return subSideBox;
    }

    return null;
  }

  protected boolean areButtonsActive(EntityPlayer player) {
    return true;
  }

  protected boolean onItemUse(
      EntityPlayerMP player, TileEntityGuide guide, int side, float hitX, float hitY, float hitZ) {
    return false;
  }

  @Override
  public boolean onSelected(World world, int x, int y, int z, DrawBlockHighlightEvent evt) {
    if (areButtonsActive(evt.player)) {
      final Vec3 hitVec = evt.target.hitVec;
      final Vec3 relPos = hitVec.addVector(-x, -y, -z);

      final Entry<AxisAlignedBB, ShapeManipulator> clickBox = findClickBox(relPos);
      selection = clickBox != null ? clickBox.getKey().getOffsetBoundingBox(x, y, z) : null;
    } else selection = null;

    return false;
  }

  @Override
  public boolean onBlockActivated(
      World world,
      int x,
      int y,
      int z,
      EntityPlayer player,
      int side,
      float hitX,
      float hitY,
      float hitZ) {
    if (player instanceof EntityPlayerMP) {
      final TileEntityGuide guide = getTileEntity(world, x, y, z, TileEntityGuide.class);

      if (guide != null) {
        final EntityPlayerMP playerMP = (EntityPlayerMP) player;

        if (areButtonsActive(playerMP)) {
          final Vec3 relPos = Vec3.createVectorHelper(hitX, hitY, hitZ);
          final Entry<AxisAlignedBB, ShapeManipulator> clickBox = findClickBox(relPos);
          if (clickBox != null) return clickBox.getValue().activate(playerMP, guide);
        }

        final ItemStack heldStack = playerMP.getHeldItem();
        if (heldStack != null) {
          if (guide.onItemUse(playerMP, heldStack, side, hitX, hitY, hitZ)) return true;
        }
      }
    }

    return true;
  }

  @Override
  @SideOnly(Side.CLIENT)
  public void randomDisplayTick(World world, int x, int y, int z, Random random) {
    world.spawnParticle("smoke", x + 0.5f, y + 0.7f, z + 0.5f, 0.0D, 0.0D, 0.0D);
    world.spawnParticle("flame", x + 0.5f, y + 0.7f, z + 0.5f, 0.0D, 0.0D, 0.0D);
  }
}