@Override
 public void update() {
   if (worldObj.isRemote) {
     return;
   }
   // TODO: Ugh, laaaame. Schedule a block update?
   if (!partsValidated) {
     partsValidated = true;
     Iterator<ClayLump> it = parts.iterator();
     while (it.hasNext()) {
       ClayLump lump = it.next();
       if (!isValidLump(lump)) {
         if (parts.size() == 1) {
           lump.asDefault();
         } else {
           it.remove();
           InvUtil.spawnItemStack(getCoord(), new ItemStack(Items.clay_ball));
         }
       }
     }
   }
   if (getState() == ClayState.WET) {
     if (!worldObj.isRaining()) {
       lastTouched++;
     }
     if (totalHeat > 0) {
       totalHeat--;
       lastTouched++;
     }
   }
   if (getState() != lastState) {
     lastState = getState();
     broadcastMessage(null, SculptState, lastState.ordinal());
   }
 }
 private void writeParts(NBTTagCompound tag) {
   NBTTagList l = new NBTTagList();
   for (ClayLump lump : parts) {
     NBTTagCompound rc_tag = new NBTTagCompound();
     lump.write(rc_tag);
     l.appendTag(rc_tag);
   }
   tag.setTag("parts", l);
 }
 public ClayLump copy() {
   ClayLump ret = new ClayLump();
   ret.minX = minX;
   ret.minY = minY;
   ret.minZ = minZ;
   ret.maxX = maxX;
   ret.maxY = maxY;
   ret.maxZ = maxZ;
   ret.icon_id = icon_id;
   ret.icon_md = icon_md;
   ret.quat = new Quaternion(quat);
   return ret;
 }
 private void updateLump(int id, ClayLump lump) {
   if (id < 0 || id >= parts.size()) {
     return;
   }
   ClayLump old = parts.get(id);
   if (old.equals(lump)) {
     return;
   }
   parts.set(id, lump);
   touch();
   if (worldObj.isRemote) {
     return;
   }
 }
  @SubscribeEvent
  @SideOnly(Side.CLIENT)
  public void renderCeramicsSelection(DrawBlockHighlightEvent event) {
    if (event.target.subHit == -1) {
      return;
    }
    Coord c = Coord.fromMop(event.player.worldObj, event.target);
    TileEntityGreenware clay = c.getTE(TileEntityGreenware.class);
    if (clay == null) {
      return;
    }
    if (event.target.subHit < 0 || event.target.subHit >= clay.parts.size()) {
      return;
    }
    event.setCanceled(true);
    EntityPlayer player = event.player;
    double partial = event.partialTicks;
    ClayLump lump = clay.parts.get(event.target.subHit);
    Block block = new Block(Material.rock);
    lump.toRotatedBlockBounds(clay, block);
    double widen = 0.002;
    double oX = player.lastTickPosX + (player.posX - player.lastTickPosX) * partial;
    double oY = player.lastTickPosY + (player.posY - player.lastTickPosY) * partial;
    double oZ = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * partial;
    AxisAlignedBB bb =
        block
            .getSelectedBoundingBox(c.w, c.toBlockPos())
            .expand(widen, widen, widen)
            .offset(-oX, -oY, -oZ);

    GL11.glEnable(GL11.GL_BLEND);
    GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
    GL11.glDisable(GL11.GL_TEXTURE_2D);
    GL11.glDepthMask(false);
    float r = 0xFF;
    GL11.glLineWidth(2.0F);
    GL11.glColor4f(0, 0, 0, 0.4F);
    // GL11.glColor4f(0x4D/r, 0x34/r, 0x7C/r, 0.8F); //#4D347C
    RenderGlobal.drawSelectionBoundingBox(bb);
    GL11.glDepthMask(true);
    GL11.glEnable(GL11.GL_TEXTURE_2D);
    GL11.glDisable(GL11.GL_BLEND);

    // TODO: If the rotation tool is selected, may draw the axis?
    // Oooooh, we could also draw the offset position for *EVERY* tool...
  }
 ClayLump extrudeLump(ClayLump against, EnumFacing dir) {
   ClayLump lump = against.copy();
   Block b = FzUtil.getTraceHelper();
   against.toBlockBounds(b);
   int wX = lump.maxX - lump.minX;
   int wY = lump.maxY - lump.minY;
   int wZ = lump.maxZ - lump.minZ;
   lump.maxX += wX * dir.getDirectionVec().getX();
   lump.maxY += wY * dir.getDirectionVec().getY();
   lump.maxZ += wZ * dir.getDirectionVec().getZ();
   lump.minX += wX * dir.getDirectionVec().getX();
   lump.minY += wY * dir.getDirectionVec().getY();
   lump.minZ += wZ * dir.getDirectionVec().getZ();
   return lump;
 }
 @Override
 public boolean addCollisionBoxesToList(
     Block ignore, AxisAlignedBB aabb, List<AxisAlignedBB> list, Entity entity) {
   Block block = new Block(Material.rock);
   ClayState state = getState();
   if (state == ClayState.WET) {
     block.setBlockBounds(0, 0, 0, 1, 1F / 8F, 1);
     AxisAlignedBB a = block.getCollisionBoundingBox(worldObj, pos, null);
     if (aabb.intersectsWith(a)) {
       list.add(a);
     }
   }
   for (ClayLump lump : parts) {
     lump.toRotatedBlockBounds(this, block);
     AxisAlignedBB a = block.getCollisionBoundingBox(worldObj, pos, null);
     if (aabb.intersectsWith(a)) {
       list.add(a);
     }
   }
   return true;
 }
 @Override
 public MovingObjectPosition collisionRayTrace(Vec3 startVec, Vec3 endVec) {
   Block block = new Block(Material.rock);
   // It's possible for the startVec to be embedded in a lump (causing it
   // to hit the opposite side), so we must move it farther away
   Vec3 d = startVec.subtract(endVec);
   double scale = 5.2; // Diagonal of a 3³. (Was initially using incrScale = 2)
   // This isn't quite right; the dVector would properly be normalized here
   // & rescaled to the max diameter. But we can survive without it.
   // Unnormalized length of dVector is 6m in surviavl mode IIRC. This'll
   // be way longer than it needs to be.
   // Why is it + instead of -? Hmm.
   startVec = startVec.add(SpaceUtil.scale(d, scale));
   MovingObjectPosition shortest = null;
   for (int i = 0; i < parts.size(); i++) {
     ClayLump lump = parts.get(i);
     lump.toRotatedBlockBounds(this, block);
     MovingObjectPosition mop = block.collisionRayTrace(worldObj, pos, startVec, endVec);
     if (mop != null) {
       mop.subHit = i;
       if (shortest == null) {
         shortest = mop;
       } else {
         Vec3 s = shortest.hitVec;
         Vec3 m = mop.hitVec;
         s = new Vec3(s.xCoord, s.yCoord, s.zCoord);
         m = new Vec3(m.xCoord, m.yCoord, m.zCoord);
         startVec = startVec.subtract(s).subtract(m);
         if (m.lengthVector() < s.lengthVector()) {
           shortest = mop;
         }
       }
     }
   }
   return shortest;
   // return super.collisionRayTrace(w, pos, startVec, endVec);
 }
 private void shareLump(int id, ClayLump selection) {
   ArrayList<Object> toSend = new ArrayList();
   toSend.add(id);
   selection.write(toSend);
   broadcastMessage(null, SculptMove, toSend.toArray());
 }
  public boolean isValidLump(ClayLump lump) {
    // check volume
    if (!(Core.cheat)) {
      int wX = lump.maxX - lump.minX;
      int wY = lump.maxY - lump.minY;
      int wZ = lump.maxZ - lump.minZ;
      int area = wX * wY * wZ;
      int max_area = 16 * 16 * 16 /* / 4 */;
      if (!FzConfig.stretchy_clay) {
        max_area /= 4;
      }
      if (area <= 0 || area > max_area) {
        return false;
      }
    }

    // check bounds
    final int B = 16 * 3;
    if (lump.minX < 0) return false;
    if (lump.minY < 0) return false;
    if (lump.minZ < 0) return false;
    if (lump.maxX > B) return false;
    if (lump.maxY > B) return false;
    if (lump.maxZ > B) return false;

    // check for free space (needs to be last, as it can mutate the world)
    Block block = FzUtil.getTraceHelper();
    for (int dx = -1; dx <= 1; dx++) {
      for (int dy = -1; dy <= 1; dy++) {
        for (int dz = -1; dz <= 1; dz++) {
          AxisAlignedBB ab =
              new AxisAlignedBB(
                  pos.getX() + dx,
                  pos.getY() + dy,
                  pos.getZ() + dz,
                  pos.getX() + dx + 1,
                  pos.getY() + dy + 1,
                  pos.getZ() + dz + 1);
          Coord c = getCoord();
          c.x += dx;
          c.y += dy;
          c.z += dz;
          lump.toRotatedBlockBounds(this, block);
          AxisAlignedBB in = block.getCollisionBoundingBox(worldObj, pos, c.getState());
          if (in != null && ab.intersectsWith(in)) {
            // This block needs to be an Extension, or this
            if (c.isAir() || c.isReplacable()) {
              c.setId(Core.registry.legacy_factory_block);
              TileEntityExtension tex = new TileEntityExtension(this);
              c.setTE(tex);
              tex.getBlockClass().enforce(c);
              continue;
            }
            TileEntity te = c.getTE();
            if (te == this) {
              continue;
            }
            if (te instanceof TileEntityExtension) {
              TileEntityExtension tex = (TileEntityExtension) te;
              if (tex.getParent() == this) {
                continue;
              }
            }
            // We used to not allow this. We just make a bit of noise instead.
            // A notification will indicate that things will be a bit messed up here.
            // FIXME: Let block collision boxes go outside the block (Notch hard-coded for fences)
            new Notice(c, "!").sendToAll();
          }
        }
      }
    }
    return true;
  }