コード例 #1
0
ファイル: NBTData.java プロジェクト: panel70/OpenClassicAPI
  /**
   * Saves the NBT data to the given file.
   *
   * @param file File to save to.
   */
  public void save(String file) {
    File f = new File(file);
    if (!f.exists()) {
      if (!f.getParentFile().exists()) {
        f.getParentFile().mkdirs();
      }

      try {
        f.createNewFile();
      } catch (IOException e) {
        OpenClassic.getLogger()
            .severe("Failed to create new file for NBTData " + this.data.getName() + "!");
        e.printStackTrace();
        return;
      }
    }

    NBTOutputStream out = null;

    try {
      out = new NBTOutputStream(new FileOutputStream(f));
      out.writeTag(this.data);
    } catch (IOException e) {
      OpenClassic.getLogger()
          .severe("Failed to open stream for NBTData " + this.data.getName() + "!");
      e.printStackTrace();
      return;
    } finally {
      IOUtils.closeQuietly(out);
    }
  }
コード例 #2
0
  public static Level load(String file) throws IOException {
    com.mojang.minecraft.level.Level level = new com.mojang.minecraft.level.Level();

    File f = new File(OpenClassic.getGame().getDirectory(), file);
    FileInputStream in = new FileInputStream(f);
    GZIPInputStream decompressor = new GZIPInputStream(in);

    DataInputStream data = new DataInputStream(decompressor);

    int magic = convert(data.readShort());

    if (magic != 1874) {
      OpenClassic.getLogger()
          .severe(
              String.format(
                  OpenClassic.getGame().getTranslator().translate("level.format-mismatch"),
                  "MCSharp v1"));
      OpenClassic.getLogger()
          .severe(OpenClassic.getGame().getTranslator().translate("level.try-mcforge"));
      return MCForgeLevelFormat.load(file);
    }

    short width = convert(data.readShort());
    short height = convert(data.readShort());
    short depth = convert(data.readShort());

    level.xSpawn = data.readShort();
    level.ySpawn = data.readShort();
    level.zSpawn = data.readShort();
    level.rotSpawn = (byte) data.readUnsignedByte();
    data.readUnsignedByte();

    byte[] blocks = new byte[width * depth * height];

    for (int z = 0; z < depth; z++) {
      for (int y = 0; y < height; y++) {
        byte[] row = new byte[height];
        data.readFully(row);

        for (int x = 0; x < width; x++) {
          blocks[(y * height + z) * width + x] = translateBlock(row[x]);
        }
      }
    }

    level.setData(width, height, depth, blocks);

    data.close();

    try {
      f.delete();
    } catch (SecurityException e) {
      e.printStackTrace();
    }

    return level.openclassic;
  }
コード例 #3
0
ファイル: ServerLevel.java プロジェクト: speaw/OpenClassic
  public ServerLevel(LevelInfo info) {
    this.name = info.getName();
    this.author = "";
    this.creationTime = System.currentTimeMillis();

    this.spawn = info.getSpawn();
    if (this.spawn != null) this.spawn.setLevel(this);

    this.width = info.getWidth();
    this.height = info.getHeight();
    this.depth = info.getDepth();
    this.waterLevel = (short) (this.height / 2);
    this.blocks = new byte[this.width * this.depth * this.height];

    this.data = new NBTData(this.name);
    this.data.load(
        OpenClassic.getGame().getDirectory().getPath() + "/levels/" + this.name + ".nbt");
    this.executor.scheduleAtFixedRate(
        new Runnable() {
          public void run() {
            try {
              physics();
            } catch (Exception e) {
              OpenClassic.getLogger()
                  .log(java.util.logging.Level.SEVERE, "Error while ticking: {0}", e);
              e.printStackTrace();
            }
          }
        },
        0,
        1000 / Constants.PHYSICS_PER_SECOND,
        TimeUnit.MILLISECONDS);
  }
コード例 #4
0
ファイル: ClassicDecoder.java プロジェクト: speaw/OpenClassic
  @Override
  protected Object decode(
      ChannelHandlerContext context, Channel channel, ChannelBuffer buffer, VoidEnum state)
      throws Exception {
    int opcode = buffer.readUnsignedByte();
    MessageCodec<?> codec = CodecLookupService.find(opcode);

    if (codec == null) {
      OpenClassic.getLogger()
          .warning(
              "Invalid packet ID "
                  + opcode
                  + "! (previous ID = "
                  + this.previousOpcode
                  + ") Disconnecting user...");

      if (context.getAttachment() instanceof ServerSession) {
        ((ServerSession) context.getAttachment()).disconnect("Invalid packet ID " + opcode + "!");
      } else {
        channel.disconnect();
      }

      return null;
    }

    this.previousOpcode = opcode;

    return codec.decode(buffer);
  }
コード例 #5
0
ファイル: ServerLevel.java プロジェクト: speaw/OpenClassic
  public void setName(String name) {
    if (this.name != null && !this.name.equals("")) return;

    this.name = name;
    this.data = new NBTData(this.name);
    this.data.load(
        OpenClassic.getGame().getDirectory().getPath() + "/levels/" + this.name + ".nbt");
  }
コード例 #6
0
ファイル: NBTData.java プロジェクト: panel70/OpenClassicAPI
  /**
   * Loads NBT data from the given file.
   *
   * @param file File to load from.
   */
  public void load(String file) {
    File f = new File(file);
    if (!f.exists()) {
      if (!f.getParentFile().exists()) {
        f.getParentFile().mkdirs();
      }

      try {
        f.createNewFile();
      } catch (IOException e) {
        OpenClassic.getLogger()
            .severe("Failed to create new file for NBTData " + this.data.getName() + "!");
        e.printStackTrace();
        return;
      }

      return;
    }

    NBTInputStream in = null;

    try {
      in = new NBTInputStream(new FileInputStream(f));
      CompoundTag tag = (CompoundTag) in.readTag();
      this.data.clear();

      for (String name : tag.keySet()) {
        this.data.put(name, tag.get(name));
      }
    } catch (EOFException e) {
      this.data.clear();
      return;
    } catch (IOException e) {
      OpenClassic.getLogger()
          .severe("Failed to open stream for NBTData " + this.data.getName() + "!");
      e.printStackTrace();
      return;
    } finally {
      IOUtils.closeQuietly(in);
    }
  }
コード例 #7
0
ファイル: ServerLevel.java プロジェクト: speaw/OpenClassic
  private static boolean physicsAllowed(Block block) {
    if (block.getType().getPhysics() == null) return false;

    BlockPhysicsEvent event = EventFactory.callEvent(new BlockPhysicsEvent(block));
    if (block.getType().getPhysics() instanceof FallingBlockPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.falling", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof FlowerPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.flower", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof MushroomPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.mushroom", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof SaplingPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.trees", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof SpongePhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.sponge", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof LiquidPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.liquid", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof GrassPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.grass", true)
          && !event.isCancelled();
    }

    return true;
  }
コード例 #8
0
ファイル: ServerLevel.java プロジェクト: speaw/OpenClassic
 public boolean treePhysics() {
   return OpenClassic.getGame().getConfig().getBoolean("physics.trees", true);
 }
コード例 #9
0
ファイル: ServerLevel.java プロジェクト: speaw/OpenClassic
public class ServerLevel implements Level {

  private static final Random rand = new Random();

  private long creationTime;
  private short width;
  private short height;
  private short depth;
  private byte[] blocks;
  private Position spawn;
  private String name;
  private String author;
  private short waterLevel;
  private boolean generating;
  private int skyColor = 10079487;
  private int fogColor = 16777215;
  private int cloudColor = 16777215;
  private List<Player> players = new ArrayList<Player>();
  private List<BlockEntity> entities = new ArrayList<BlockEntity>();

  private boolean physics = OpenClassic.getGame().getConfig().getBoolean("physics.enabled", true);
  private TripleIntHashMap<Integer> physicsQueue = new TripleIntHashMap<Integer>();
  private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

  private NBTData data;

  public ServerLevel() {
    this.executor.scheduleAtFixedRate(
        new Runnable() {
          public void run() {
            try {
              physics();
            } catch (Exception e) {
              OpenClassic.getLogger()
                  .log(java.util.logging.Level.SEVERE, "Error while ticking physics: {0}", e);
              e.printStackTrace();
            }
          }
        },
        0,
        1000 / Constants.PHYSICS_PER_SECOND,
        TimeUnit.MILLISECONDS);
  }

  public ServerLevel(LevelInfo info) {
    this.name = info.getName();
    this.author = "";
    this.creationTime = System.currentTimeMillis();

    this.spawn = info.getSpawn();
    if (this.spawn != null) this.spawn.setLevel(this);

    this.width = info.getWidth();
    this.height = info.getHeight();
    this.depth = info.getDepth();
    this.waterLevel = (short) (this.height / 2);
    this.blocks = new byte[this.width * this.depth * this.height];

    this.data = new NBTData(this.name);
    this.data.load(
        OpenClassic.getGame().getDirectory().getPath() + "/levels/" + this.name + ".nbt");
    this.executor.scheduleAtFixedRate(
        new Runnable() {
          public void run() {
            try {
              physics();
            } catch (Exception e) {
              OpenClassic.getLogger()
                  .log(java.util.logging.Level.SEVERE, "Error while ticking: {0}", e);
              e.printStackTrace();
            }
          }
        },
        0,
        1000 / Constants.PHYSICS_PER_SECOND,
        TimeUnit.MILLISECONDS);
  }

  public boolean getPhysicsEnabled() {
    return this.physics;
  }

  public void setPhysicsEnabled(boolean enabled) {
    this.physics = enabled;
  }

  public void addPlayer(Player player) {
    this.players.add(player);
  }

  public void removePlayer(String name) {
    for (Player player : this.getPlayers()) {
      if (player.getName().equalsIgnoreCase(name)) {
        this.players.remove(player);
        this.sendToAllExcept(player, new PlayerDespawnMessage(player.getPlayerId()));
      }
    }
  }

  public void removePlayer(byte id) {
    for (Player player : this.getPlayers()) {
      if (player.getPlayerId() == id) {
        this.players.remove(player);
        this.sendToAllExcept(player, new PlayerDespawnMessage(player.getPlayerId()));
      }
    }
  }

  public List<Player> getPlayers() {
    return new ArrayList<Player>(this.players);
  }

  public void tick() {
    for (Player player : this.players) {
      ((ServerPlayer) player).tick();
    }

    for (BlockEntity entity : this.entities) {
      if (entity.getController() != null) entity.getController().tick();
    }
  }

  // TODO: Idle physics like grass, flower, etc
  public void physics() {
    if (this.physics) {
      String updates[];
      Integer ticks[];
      synchronized (this.physicsQueue) {
        updates = this.physicsQueue.keySet().toArray(new String[this.physicsQueue.size()]);
        ticks = this.physicsQueue.values().toArray(new Integer[this.physicsQueue.size()]);
        this.physicsQueue.clear();
      }

      for (int count = 0; count < updates.length; count++) {
        int x = TripleIntHashMap.key1(updates[count]);
        int y = TripleIntHashMap.key2(updates[count]);
        int z = TripleIntHashMap.key3(updates[count]);

        Block block = this.getBlockAt(x, y, z);
        int tick = ticks[count];
        tick--;
        if (tick > 0) {
          synchronized (this.physicsQueue) {
            this.physicsQueue.put(x, y, z, tick);
          }

          continue;
        }

        if (physicsAllowed(block)) {
          block.getType().getPhysics().update(block);
        }
      }
    }
  }

  public void clearPhysics() {
    synchronized (this.physicsQueue) {
      this.physicsQueue.clear();
    }
  }

  public void dispose() {
    this.executor.shutdown();
  }

  public void updatePhysics(int x, int y, int z) {
    this.updatePhysics(x, y, z, true);
  }

  public void updatePhysics(int x, int y, int z, boolean around) {
    if (!this.physics) return;

    synchronized (this.physicsQueue) {
      this.physicsQueue.put(x, y, z, this.getBlockTypeAt(x, y, z).getTickDelay());

      if (around) {
        // this.physicsQueue.add(x + 1, y, z);
        // this.physicsQueue.add(x - 1, y, z);
        // this.physicsQueue.add(x, y + 1, z);
        // this.physicsQueue.add(x, y - 1, z);
        // this.physicsQueue.add(x, y, z + 1);
        // this.physicsQueue.add(x, y, z - 1);
      }
    }
  }

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    if (this.name != null && !this.name.equals("")) return;

    this.name = name;
    this.data = new NBTData(this.name);
    this.data.load(
        OpenClassic.getGame().getDirectory().getPath() + "/levels/" + this.name + ".nbt");
  }

  public String getAuthor() {
    return this.author;
  }

  public void setAuthor(String author) {
    if (this.author != null && !this.author.equals("")) return;

    this.author = author;
  }

  public long getCreationTime() {
    return this.creationTime;
  }

  public void setCreationTime(long time) {
    if (this.creationTime != 0) return;

    this.creationTime = time;
  }

  public Position getSpawn() {
    return this.spawn;
  }

  public void setSpawn(Position spawn) {
    Position old = this.spawn;
    this.spawn = spawn;
    EventFactory.callEvent(new SpawnChangeEvent(this, old));
  }

  public short getWidth() {
    return this.width;
  }

  public short getHeight() {
    return this.height;
  }

  public short getDepth() {
    return this.depth;
  }

  public short getWaterLevel() {
    return this.waterLevel;
  }

  public byte[] getBlocks() {
    return Arrays.copyOf(this.blocks, this.blocks.length);
  }

  public void setWorldData(short width, short height, short depth, byte[] blocks) {
    this.width = width;
    this.height = height;
    this.depth = depth;
    this.waterLevel = (short) (this.height / 2);

    this.blocks = blocks;
  }

  public byte getBlockIdAt(Position pos) {
    return this.getBlockIdAt(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
  }

  public byte getBlockIdAt(int x, int y, int z) {
    if (x < 0 || y < 0 || z < 0 || x >= this.width || y >= this.height || z >= this.depth) return 0;

    return blocks[coordsToBlockIndex(x, y, z)];
  }

  public BlockType getBlockTypeAt(Position pos) {
    return this.getBlockTypeAt(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
  }

  public BlockType getBlockTypeAt(int x, int y, int z) {
    if (x < 0 || y < 0 || z < 0 || x >= this.width || y >= this.height || z >= this.depth)
      return VanillaBlock.AIR;

    return Blocks.fromId(this.getBlockIdAt(x, y, z));
  }

  public Block getBlockAt(Position pos) {
    if (pos.getBlockX() < 0
        || pos.getBlockY() < 0
        || pos.getBlockZ() < 0
        || pos.getBlockX() >= this.width
        || pos.getBlockY() >= this.height
        || pos.getBlockZ() >= this.depth) return null;

    return new Block(pos);
  }

  public Block getBlockAt(int x, int y, int z) {
    return this.getBlockAt(new Position(this, x, y, z));
  }

  public boolean setBlockIdAt(Position pos, byte type) {
    return this.setBlockIdAt(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), type);
  }

  public boolean setBlockIdAt(Position pos, byte type, boolean physics) {
    return this.setBlockIdAt(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), type, physics);
  }

  public boolean setBlockIdAt(int x, int y, int z, byte type) {
    return this.setBlockIdAt(x, y, z, type, true);
  }

  public boolean setBlockIdAt(int x, int y, int z, byte type, boolean physics) {
    if (x < 0 || y < 0 || z < 0 || x >= this.width || y >= this.height || z >= this.depth)
      return false;

    if (!this.generating
        && type == VanillaBlock.AIR.getId()
        && (x == 0 || x == this.getWidth() - 1 || z == 0 || z == this.getDepth() - 1)
        && y <= this.getWaterLevel() - 1
        && y > this.getWaterLevel() - 3) {
      type = VanillaBlock.WATER.getId();
    }

    blocks[coordsToBlockIndex(x, y, z)] = type;
    this.sendToAll(new BlockChangeMessage((short) x, (short) y, (short) z, type));

    if (physics && !this.generating) {
      for (BlockFace face : BlockFace.values()) {
        Block block = this.getBlockAt(x + face.getModX(), y + face.getModY(), z + face.getModZ());
        if (block != null && block.getType() != null && block.getType().getPhysics() != null) {
          block.getType().getPhysics().onNeighborChange(block, this.getBlockAt(x, y, z));
        }
      }
    }

    return true;
  }

  public boolean setBlockAt(Position pos, BlockType type) {
    return this.setBlockAt(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), type);
  }

  public boolean setBlockAt(Position pos, BlockType type, boolean physics) {
    return this.setBlockAt(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), type, physics);
  }

  public boolean setBlockAt(int x, int y, int z, BlockType type) {
    return this.setBlockAt(x, y, z, type, true);
  }

  public boolean setBlockAt(int x, int y, int z, BlockType type, boolean physics) {
    return this.setBlockIdAt(x, y, z, type.getId(), physics);
  }

  @Override
  public int getHighestBlockY(int x, int z) {
    return this.getHighestBlockY(x, z, this.getHeight());
  }

  @Override
  public int getHighestBlockY(int x, int z, int max) {
    for (int y = max; y >= 0; y--) {
      if (this.getBlockIdAt(x, y, z) != 0) return y;
    }

    return -1;
  }

  @Override
  public boolean isHighest(int x, int y, int z) {
    if (this.getHighestBlockY(x, z) <= y) return true;
    return false;
  }

  @Override
  public boolean isLit(int x, int y, int z) {
    boolean lit = false;

    for (int curr = y; curr <= this.getHeight(); curr++) {
      if (!this.canLightPass(this.getBlockAt(x, curr, z))) lit = false;
    }

    return lit;
  }

  public boolean canLightPass(Block block) {
    return block == null
        || block.getType() == VanillaBlock.AIR
        || block.getType() == VanillaBlock.DANDELION
        || block.getType() == VanillaBlock.ROSE
        || block.getType() == VanillaBlock.RED_MUSHROOM
        || block.getType() == VanillaBlock.BROWN_MUSHROOM
        || block.getType() == VanillaBlock.GLASS
        || block.getType() == VanillaBlock.LEAVES;
  }

  public boolean isGenerating() {
    return this.generating;
  }

  public void setGenerating(boolean generating) {
    this.generating = generating;
  }

  public boolean treePhysics() {
    return OpenClassic.getGame().getConfig().getBoolean("physics.trees", true);
  }

  public int coordsToBlockIndex(int x, int y, int z) {
    if (x < 0 || y < 0 || z < 0 || x >= this.width || y >= this.height || z >= this.depth)
      return -1;

    return x + (z * this.width) + (y * this.width * this.depth);
  }

  public Position blockIndexToCoords(int index) {
    if (index < 0) return null;

    int y = index / this.width / this.depth;
    index -= y * this.width * this.depth;

    int z = index / this.width;
    int x = index - z * this.width;

    return new Position(this, x, y, z);
  }

  public static int coordsToBlockIndex(int x, int y, int z, int width, int height, int depth) {
    if (x < 0 || y < 0 || z < 0 || x >= width || y >= height || z >= depth) return -1;

    return x + (z * width) + (y * width * depth);
  }

  public static Position blockIndexToCoords(
      int index, ServerLevel level, int width, int height, int depth) {
    if (index < 0) return null;

    int y = index / width / depth;
    index -= y * width * depth;

    int z = index / width;
    int x = index - z * width;

    return new Position(level, x, y, z);
  }

  public void sendToAll(Message message) {
    for (Player player : this.getPlayers()) {
      player.getSession().send(message);
    }
  }

  public void sendToAllExcept(Player skip, Message message) {
    for (Player player : this.getPlayers()) {
      if (player.getPlayerId() == skip.getPlayerId()) continue;

      player.getSession().send(message);
    }
  }

  public List<BlockEntity> getBlockEntities() {
    return new ArrayList<BlockEntity>(this.entities);
  }

  public BlockEntity getBlockEntityFromId(int id) {
    for (BlockEntity entity : this.entities) {
      if (entity.getEntityId() == id) return entity;
    }

    return null;
  }

  public BlockEntity getBlockEntity(Position pos) {
    for (BlockEntity entity : this.entities) {
      if (entity.getPosition().equals(pos)) return entity;
    }

    return null;
  }

  public BlockEntity spawnBlockEntity(BlockEntity entity, Position pos) {
    this.entities.add(entity);
    entity.setPosition(pos);

    return entity;
  }

  public void removeBlockEntity(BlockEntity entity) {
    this.removeBlockEntity(entity.getEntityId());
  }

  public void removeBlockEntity(int id) {
    for (BlockEntity entity : this.entities) {
      if (entity.getEntityId() == id) {
        if (entity.getController() != null) entity.getController().onDeath();
        EventFactory.callEvent(new EntityDeathEvent(entity));
        this.entities.remove(entity);
      }
    }
  }

  private static boolean physicsAllowed(Block block) {
    if (block.getType().getPhysics() == null) return false;

    BlockPhysicsEvent event = EventFactory.callEvent(new BlockPhysicsEvent(block));
    if (block.getType().getPhysics() instanceof FallingBlockPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.falling", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof FlowerPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.flower", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof MushroomPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.mushroom", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof SaplingPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.trees", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof SpongePhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.sponge", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof LiquidPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.liquid", true)
          && !event.isCancelled();
    }

    if (block.getType().getPhysics() instanceof GrassPhysics) {
      return OpenClassic.getGame().getConfig().getBoolean("physics.grass", true)
          && !event.isCancelled();
    }

    return true;
  }

  @Override
  public void delayTick(Position pos, byte id) {
    this.updatePhysics(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(), false);
  }

  public boolean growTree(int x, int y, int z) {
    int logHeight = rand.nextInt(3) + 4;
    boolean freespace = true;

    for (int currY = y; currY <= y + 1 + logHeight; currY++) {
      byte leaf = 1;
      if (currY == y) {
        leaf = 0;
      }

      if (currY >= y + 1 + logHeight - 2) {
        leaf = 2;
      }

      for (int currX = x - leaf; currX <= x + leaf && freespace; ++currX) {
        for (int currZ = z - leaf; currZ <= z + leaf && freespace; ++currZ) {
          if (currX >= 0
              && currY >= 0
              && currZ >= 0
              && currX < this.width
              && currY < this.depth
              && currZ < this.height) {
            if (this.getBlockTypeAt(currX, currY, currZ) != VanillaBlock.AIR) {
              freespace = false;
            }
          } else {
            freespace = false;
          }
        }
      }
    }

    if (!freespace) {
      return false;
    } else if (this.getBlockTypeAt(x, y, z) == VanillaBlock.GRASS
        && y < this.depth - logHeight - 1) {
      this.setBlockAt(x, y - 1, z, VanillaBlock.DIRT);
      for (int count = y - 3 + logHeight; count <= y + logHeight; ++count) {
        int var8 = count - (y + logHeight);
        int leafMax = 1 - var8 / 2;

        for (int currX = x - leafMax; currX <= x + leafMax; ++currX) {
          int diffX = currX - x;

          for (int currZ = z - leafMax; currZ <= z + leafMax; ++currZ) {
            int diffZ = currZ - z;
            if (Math.abs(diffX) != leafMax
                || Math.abs(diffZ) != leafMax
                || rand.nextInt(2) != 0 && var8 != 0) {
              this.setBlockAt(currX, count, currZ, VanillaBlock.LEAVES);
            }
          }
        }
      }

      for (int count = 0; count < logHeight; ++count) {
        this.setBlockAt(x, y + count, z, VanillaBlock.LOG);
      }

      return true;
    } else {
      return false;
    }
  }

  public NBTData getData() {
    return this.data;
  }

  @Override
  public int getSkyColor() {
    return this.skyColor;
  }

  @Override
  public void setSkyColor(int color) {
    this.skyColor = color;
    this.sendToAll(new LevelColorMessage("sky", color));
  }

  @Override
  public int getFogColor() {
    return this.fogColor;
  }

  @Override
  public void setFogColor(int color) {
    this.fogColor = color;
    this.sendToAll(new LevelColorMessage("fog", color));
  }

  @Override
  public int getCloudColor() {
    return this.cloudColor;
  }

  @Override
  public void setCloudColor(int color) {
    this.cloudColor = color;
    this.sendToAll(new LevelColorMessage("cloud", color));
  }
}