public void readUpdatePacketData(NBTTagCompound tag) {

    int amt = tag.getInteger("partAmount");
    List<BPPart> found = new ArrayList<BPPart>();
    for (int i = 0; i < amt; i++) {
      String type = tag.getString("partType" + i);
      UUID id = UUID.fromString(tag.getString("partId" + i));
      NBTTagCompound t = tag.getCompoundTag("part" + i);
      BPPart p = getPartForId(id);
      if (p == null) addPart(p = PartRegistry.getInstance().createPart(type), id);
      p.load(t);
      found.add(p);
    }
    List<BPPart> removed = new ArrayList<BPPart>();
    for (BPPart p : getParts()) {
      if (!found.contains(p)) {
        removed.add(p);
      }
    }
    for (BPPart p : removed) removePart(p);

    found.clear();
    removed.clear();

    shouldReRender = true;
  }
  public void removePart(BPPart part) {

    parts.remove(part);
    partIds.remove(part);
    shouldReRender = true;
    for (BPPart p : parts) if (p != part) p.onPartChanged();
    sendUpdatePacket();
  }
  public float getExplosionResistance() {

    float res = 0;

    for (BPPart p : parts) res = Math.max(res, p.getExplosionResistance());

    return res;
  }
  public void notifyPartChange(BPPart part) {

    for (BPPart p : parts) {
      if (p == part) continue;
      p.onPartChanged();
    }
    shouldReRender = true;
  }
  public int getLightValue() {

    int val = 0;

    for (BPPart p : parts) val = Math.max(val, p.getLightValue());

    return Math.max(0, Math.min(val, 15));
  }
  @SideOnly(Side.CLIENT)
  public void renderStatic(Vector3 loc, int pass) {

    for (BPPart p : parts) {
      GL11.glPushMatrix();
      p.renderStatic(loc, pass);
      GL11.glPopMatrix();
    }
  }
  @SideOnly(Side.CLIENT)
  public void renderDynamic(Vector3 loc, int pass, float frame) {

    for (BPPart p : parts) {
      GL11.glPushMatrix();
      p.renderDynamic(loc, pass, frame);
      GL11.glPopMatrix();
    }
  }
  private void addPart(BPPart part, UUID id) {

    parts.add(part);
    partIds.put(part, id);
    shouldReRender = true;
    for (BPPart p : parts) if (p != part) p.onPartChanged();
    notifyNeighbors();
    sendUpdatePacket();
  }
  public boolean shouldReRender() {

    boolean should = shouldReRender;
    for (BPPart p : getParts()) {
      if (p.shouldReRender()) {
        should = true;
        p.resetRenderUpdate();
      }
    }
    shouldReRender = false;
    return should;
  }
  @Override
  public void readFromNBT(NBTTagCompound tag) {

    super.readFromNBT(tag);
    int amt = tag.getInteger("partAmount");
    for (int i = 0; i < amt; i++) {
      String type = tag.getString("partType" + i);
      NBTTagCompound t = tag.getCompoundTag("part" + i);
      BPPart p = PartRegistry.getInstance().createPart(type);
      p.load(t);
      addPart(p);
    }
  }
  public MovingObjectPosition rayTrace(Vector3 start, Vector3 end) {

    List<MovingObjectPosition> mops = new ArrayList<MovingObjectPosition>();

    for (BPPart p : parts) {
      MovingObjectPosition mop = p.rayTrace(start, end);
      if (mop != null) mops.add(mop);
    }

    Collections.sort(mops, new ComparatorMOP(start));

    if (mops.isEmpty()) return null;

    return mops.get(0);
  }
  @Override
  public void writeToNBT(NBTTagCompound tag) {

    super.writeToNBT(tag);

    int i = 0;
    for (BPPart p : parts) {
      NBTTagCompound t = new NBTTagCompound();
      p.save(t);
      tag.setString("partType" + i, p.getType());
      tag.setTag("part" + i, t);
      i++;
    }
    tag.setInteger("partAmount", i);
  }
  public NBTTagCompound getUpdatePacketData() {

    NBTTagCompound tag = new NBTTagCompound();

    int i = 0;
    for (BPPart p : parts) {
      NBTTagCompound t = new NBTTagCompound();
      p.save(t);
      tag.setString("partType" + i, p.getType());
      tag.setString("partId" + i, partIds.get(p).toString());
      tag.setTag("part" + i, t);
      i++;
    }
    tag.setInteger("partAmount", i);

    return tag;
  }
  public boolean canConnectRedstone(ForgeDirection side) {

    for (BPPart p : parts) {
      p.setWorld(worldObj);
      p.setX(xCoord);
      p.setY(yCoord);
      p.setZ(zCoord);
      if (p instanceof IBPRedstonePart) {
        ForgeDirection s = side;
        if (p instanceof IBPFacePart)
          s = s.getRotation(ForgeDirection.getOrientation(((IBPFacePart) p).getFace()));
        if (((IBPRedstonePart) p).canConnect(s)) return true;
      }
    }

    return false;
  }
  @Override
  public void updateEntity() {

    if (parts.size() == 0 && !worldObj.isRemote) {
      worldObj.setBlock(xCoord, yCoord, zCoord, Blocks.air);
      worldObj.setTileEntity(xCoord, yCoord, zCoord, null);
      return;
    }

    List<BPPart> removed = new ArrayList<BPPart>();
    for (BPPart p : parts) {
      p.setWorld(worldObj);
      p.setX(xCoord);
      p.setY(yCoord);
      p.setZ(zCoord);
      if (p instanceof BPPartFace) if (!((BPPartFace) p).canStay()) removed.add(p);
    }
    for (BPPart p : removed) removePart(p);
    removed.clear();

    for (BPPart p : parts) p.update();

    if (ticks == 5 || ticks % 200 == 0) {
      shouldReRender = true;
    }
    ticks++;
  }
  public void onEntityCollision(Entity entity) {

    for (BPPart p : parts) {
      p.setWorld(worldObj);
      p.setX(xCoord);
      p.setY(yCoord);
      p.setZ(zCoord);
      p.onEntityCollision(entity); // FIXME BLUEPOWER Check if entity is actually colliding
    }
  }
  public void onNeighborUpdate() {

    for (BPPart p : parts) {
      p.setWorld(worldObj);
      p.setX(xCoord);
      p.setY(yCoord);
      p.setZ(zCoord);
      p.onNeighborUpdate();
    }

    shouldReRender = true;
  }
  public List<AxisAlignedBB> getOcclusionBoxes() {

    List<AxisAlignedBB> aabbs = new ArrayList<AxisAlignedBB>();
    for (BPPart p : parts) aabbs.addAll(p.getOcclusionBoxes());
    return aabbs;
  }
  public boolean isFaceSolid(ForgeDirection orientation) {

    for (BPPart p : parts) if (p.isFaceSolid(orientation)) return true;

    return false;
  }