/**
  * @param mechname
  * @return true if the bracketed keyword on the sign matches the given mechname; false otherwise
  *     or if no sign.
  * @throws ClassCastException if the declarative constructor was used in such a way that a
  *     non-sign block was specified for a sign.
  */
 public boolean matches(String mechname) {
   return (sign == null)
       ? false
       : (((Sign) sign.getState()).getLine(1).equalsIgnoreCase("[" + mechname + "]"));
   // the astute will notice there's a problem coming up here with the one dang thing that had to
   // go and break the mold with second line definer.
 }
 /**
  * Detecting factory, based on the position of the base. The rails must be one block above and the
  * sign if it exists must be one or two blocks below. Signs are guaranteed to be signs (unless
  * they're null) and rails are guaranteed to be rails.
  *
  * @param base the block on which the rails sit; the type of this block is what determines the
  *     mechanism type.
  */
 public static CartMechanismBlocks findByBase(Block base) throws InvalidMechanismException {
   if (!BlockType.isRailBlock(base.getFace(BlockFace.UP, 1).getTypeId()))
     throw new InvalidMechanismException("could not find rails.");
   if (SignUtil.isSign(base.getFace(BlockFace.DOWN, 1).getTypeId())) {
     return new CartMechanismBlocks(
         base.getFace(BlockFace.UP, 1), base, base.getFace(BlockFace.DOWN, 1));
   } else if (SignUtil.isSign(base.getFace(BlockFace.DOWN, 2).getTypeId())) {
     return new CartMechanismBlocks(
         base.getFace(BlockFace.UP, 1), base, base.getFace(BlockFace.DOWN, 2));
   }
   return new CartMechanismBlocks(base.getFace(BlockFace.UP, 1), base, null);
 }
 /**
  * Detecting factory, based on the position of the rails. The base must be one block below and the
  * sign if it exists must be two or three blocks below. Signs are guaranteed to be signs (unless
  * they're null) and rails are guaranteed to be rails.
  *
  * <p>This is the most important constructor, since it is the one invoked when processing cart
  * move events.
  *
  * @param rail the block containing the rails.
  */
 public static CartMechanismBlocks findByRail(Block rail) throws InvalidMechanismException {
   if (!BlockType.isRailBlock(rail.getTypeId()))
     throw new InvalidMechanismException("rail argument must be a rail!");
   if (SignUtil.isSign(rail.getFace(BlockFace.DOWN, 2).getTypeId())) {
     return new CartMechanismBlocks(
         rail, rail.getFace(BlockFace.DOWN, 1), rail.getFace(BlockFace.DOWN, 2));
   } else if (SignUtil.isSign(rail.getFace(BlockFace.DOWN, 3).getTypeId())) {
     return new CartMechanismBlocks(
         rail, rail.getFace(BlockFace.DOWN, 1), rail.getFace(BlockFace.DOWN, 3));
   }
   return new CartMechanismBlocks(rail, rail.getFace(BlockFace.DOWN, 1), null);
 }
 /**
  * Detecting factory, based on the position of the sign. The base must be one or two blocks above
  * and the rails an additional block above the base. Signs are guaranteed to be signs and rails
  * are guaranteed to be rails.
  *
  * @param sign the block containing the sign that gives additional configuration to the mechanism.
  */
 public static CartMechanismBlocks findBySign(Block sign) throws InvalidMechanismException {
   if (!SignUtil.isSign(sign))
     throw new InvalidMechanismException("sign argument must be a sign!");
   if (BlockType.isRailBlock(sign.getFace(BlockFace.UP, 2).getTypeId())) {
     return new CartMechanismBlocks(
         sign.getFace(BlockFace.UP, 2), sign.getFace(BlockFace.UP, 1), sign);
   } else if (BlockType.isRailBlock(sign.getFace(BlockFace.UP, 3).getTypeId())) {
     return new CartMechanismBlocks(
         sign.getFace(BlockFace.UP, 3), sign.getFace(BlockFace.UP, 2), sign);
   }
   throw new InvalidMechanismException("could not find rails.");
 }
 PerformResult perform() throws WorldEditorException {
   if (dontRollback.contains(replaced)) return PerformResult.BLACKLISTED;
   final Block block = loc.getBlock();
   if (replaced == 0 && block.getTypeId() == 0) return PerformResult.NO_ACTION;
   final BlockState state = block.getState();
   if (!world.isChunkLoaded(block.getChunk())) world.loadChunk(block.getChunk());
   if (type == replaced) {
     if (type == 0) {
       if (!block.setTypeId(0))
         throw new WorldEditorException(block.getTypeId(), 0, block.getLocation());
     } else if (ca != null && (type == 23 || type == 54 || type == 61 || type == 62)) {
       int leftover = 0;
       try {
         leftover =
             modifyContainer(
                 state, new ItemStack(ca.itemType, -ca.itemAmount, (short) 0, ca.itemData));
         if (leftover > 0)
           for (final BlockFace face :
               new BlockFace[] {
                 BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST
               })
             if (block.getRelative(face).getTypeId() == 54)
               leftover =
                   modifyContainer(
                       block.getRelative(face).getState(),
                       new ItemStack(
                           ca.itemType,
                           ca.itemAmount < 0 ? leftover : -leftover,
                           (short) 0,
                           ca.itemData));
       } catch (final Exception ex) {
         throw new WorldEditorException(ex.getMessage(), block.getLocation());
       }
       if (!state.update())
         throw new WorldEditorException(
             "Failed to update inventory of " + materialName(block.getTypeId()),
             block.getLocation());
       if (leftover > 0 && ca.itemAmount < 0)
         throw new WorldEditorException(
             "Not enough space left in " + materialName(block.getTypeId()), block.getLocation());
     } else return PerformResult.NO_ACTION;
     return PerformResult.SUCCESS;
   }
   if (!(equalTypes(block.getTypeId(), type) || replaceAnyway.contains(block.getTypeId())))
     return PerformResult.NO_ACTION;
   if (state instanceof InventoryHolder) {
     ((InventoryHolder) state).getInventory().clear();
     state.update();
   }
   if (block.getTypeId() == replaced) {
     if (block.getData() != (type == 0 ? data : (byte) 0))
       block.setData(type == 0 ? data : (byte) 0, true);
     else return PerformResult.NO_ACTION;
   } else if (!block.setTypeIdAndData(replaced, type == 0 ? data : (byte) 0, true))
     throw new WorldEditorException(block.getTypeId(), replaced, block.getLocation());
   final int curtype = block.getTypeId();
   if (signtext != null && (curtype == 63 || curtype == 68)) {
     final Sign sign = (Sign) block.getState();
     final String[] lines = signtext.split("\0", 4);
     if (lines.length < 4) return PerformResult.NO_ACTION;
     for (int i = 0; i < 4; i++) sign.setLine(i, lines[i]);
     if (!sign.update())
       throw new WorldEditorException(
           "Failed to update signtext of " + materialName(block.getTypeId()),
           block.getLocation());
   } else if (curtype == 26) {
     final Bed bed = (Bed) block.getState().getData();
     final Block secBlock =
         bed.isHeadOfBed()
             ? block.getRelative(bed.getFacing().getOppositeFace())
             : block.getRelative(bed.getFacing());
     if (secBlock.getTypeId() == 0
         && !secBlock.setTypeIdAndData(26, (byte) (bed.getData() | 8), true))
       throw new WorldEditorException(secBlock.getTypeId(), 26, secBlock.getLocation());
   } else if (curtype == 64 || curtype == 71) {
     final byte blockData = block.getData();
     final Block secBlock =
         (blockData & 8) == 8
             ? block.getRelative(BlockFace.DOWN)
             : block.getRelative(BlockFace.UP);
     if (secBlock.getTypeId() == 0
         && !secBlock.setTypeIdAndData(curtype, (byte) (blockData | 8), true))
       throw new WorldEditorException(secBlock.getTypeId(), curtype, secBlock.getLocation());
   } else if ((curtype == 29 || curtype == 33) && (block.getData() & 8) > 0) {
     final PistonBaseMaterial piston = (PistonBaseMaterial) block.getState().getData();
     final Block secBlock = block.getRelative(piston.getFacing());
     if (secBlock.getTypeId() == 0
         && !secBlock.setTypeIdAndData(
             34,
             curtype == 29 ? (byte) (block.getData() | 8) : (byte) (block.getData() & ~8),
             true))
       throw new WorldEditorException(secBlock.getTypeId(), 34, secBlock.getLocation());
   } else if (curtype == 34) {
     final PistonExtensionMaterial piston = (PistonExtensionMaterial) block.getState().getData();
     final Block secBlock = block.getRelative(piston.getFacing().getOppositeFace());
     if (secBlock.getTypeId() == 0
         && !secBlock.setTypeIdAndData(
             piston.isSticky() ? 29 : 33, (byte) (block.getData() | 8), true))
       throw new WorldEditorException(
           secBlock.getTypeId(), piston.isSticky() ? 29 : 33, secBlock.getLocation());
   } else if (curtype == 18 && (block.getData() & 8) > 0)
     block.setData((byte) (block.getData() & 0xF7));
   return PerformResult.SUCCESS;
 }
 /**
  * Detecting factory; defers to one of the other three specific detecting factories based on
  * whether the given unknown block appears to be a sign, rail, or base.
  *
  * @param unknown the block to examine.
  */
 public static CartMechanismBlocks find(Block unknown) throws InvalidMechanismException {
   final int ti = unknown.getTypeId();
   if (SignUtil.isSign(ti)) return findBySign(unknown);
   else if (BlockType.isRailBlock(ti)) return findByRail(unknown);
   else return findByBase(unknown);
 }
 /**
  * @return a Sign BlockState, or null if there is no sign block.
  * @throws ClassCastException if there a sign block is set, but it's not *actually* a sign block.
  */
 public Sign getSign() {
   return (sign == null) ? null : (Sign) sign.getState();
 }
 // this tends to be redundant if checked within a CartMechanism, since it's axiomatic that that
 // CartMechanism by virtue of its configured base material.
 public boolean matches(Material mat) {
   return (base.getType() == mat);
 }