private boolean isNearWater(GlowBlock block) {
   // check around for some water blocks
   final GlowWorld world = block.getWorld();
   for (int x = block.getX() - 4; x <= block.getX() + 4; x++) {
     for (int z = block.getZ() - 4; z <= block.getZ() + 4; z++) {
       for (int y = block.getY(); y <= block.getY() + 1; y++) {
         final Material type = world.getBlockAt(x, y, z).getType();
         if (type == Material.WATER || type == Material.STATIONARY_WATER) {
           return true;
         }
       }
     }
   }
   return false;
 }
 private boolean hasNearVineBlocks(GlowBlock block) {
   GlowWorld world = block.getWorld();
   int vineCount = 0;
   for (int x = 0; x < 9; x++) {
     for (int z = 0; z < 9; z++) {
       for (int y = 0; y < 3; y++) {
         if (world.getBlockAt(block.getLocation().add(x - 4, y - 1, z - 4)).getType()
             == Material.VINE) {
           if (++vineCount >= 5) {
             return true;
           }
         }
       }
     }
   }
   return false;
 }
  public void writeWorldData() throws IOException {
    Map<String, Tag> out = new HashMap<String, Tag>();
    File uuidFile = new File(dir, "uid.dat");
    if (!uuidFile.exists()) {
      try {
        uuidFile.createNewFile();
      } catch (IOException e) {
        handleWorldException("uid.dat", e);
      }
    } else {
      UUID uuid = world.getUID();
      DataOutputStream str = new DataOutputStream(new FileOutputStream(uuidFile));
      str.writeLong(uuid.getLeastSignificantBits());
      str.writeLong(uuid.getMostSignificantBits());
      str.close();
    }
    out.putAll(unknownTags);
    unknownTags.clear();
    // Normal level data
    out.put("thundering", new ByteTag("thundering", (byte) (world.isThundering() ? 1 : 0)));
    out.put("RandomSeed", new LongTag("RandomSeed", world.getSeed()));
    out.put("Time", new LongTag("Time", world.getTime()));
    out.put("raining", new ByteTag("raining", (byte) (world.hasStorm() ? 1 : 0)));
    out.put("thunderTime", new IntTag("thunderTime", world.getThunderDuration()));
    out.put("rainTime", new IntTag("rainTime", world.getWeatherDuration()));
    Location loc = world.getSpawnLocation();
    out.put("SpawnX", new IntTag("SpawnX", loc.getBlockX()));
    out.put("SpawnY", new IntTag("SpawnY", loc.getBlockY()));
    out.put("SpawnZ", new IntTag("SpawnZ", loc.getBlockZ()));
    // Format-specific
    out.put("LevelName", new StringTag("LevelName", world.getName()));
    out.put("LastPlayed", new LongTag("LastPlayed", Calendar.getInstance().getTimeInMillis()));
    out.put("version", new IntTag("version", 19132));

    if (!out.containsKey("SizeOnDisk"))
      out.put(
          "SizeOnDisk",
          new LongTag("SizeOnDisk", 0)); // Not sure how to calculate this, so ignoring for now
    try {
      NBTOutputStream nbtOut =
          new NBTOutputStream(new FileOutputStream(new File(dir, "level.dat")));
      nbtOut.writeTag(new CompoundTag("Data", out));
      nbtOut.close();
    } catch (IOException e) {
      handleWorldException("level.dat", e);
    }
  }
 @Override
 public void blockDestroy(GlowPlayer player, GlowBlock block, BlockFace face) {
   // vanilla set leaf decay check in a 9x9x9 neighboring when a log block is removed
   final GlowWorld world = block.getWorld();
   for (int x = 0; x < 9; x++) {
     for (int z = 0; z < 9; z++) {
       for (int y = 0; y < 9; y++) {
         final GlowBlock b = world.getBlockAt(block.getLocation().add(x - 4, y - 4, z - 4));
         if (b.getType() == Material.LEAVES || b.getType() == Material.LEAVES_2) {
           final GlowBlockState state = b.getState();
           if ((state.getRawData() & 0x08) == 0
               && (state.getRawData() & 0x04) == 0) { // check decay is off and decay is on
             // set decay check on for this leaves block
             state.setRawData((byte) (state.getRawData() | 0x08));
             state.update(true);
           }
         }
       }
     }
   }
 }
  public void readPlayerData(GlowPlayer player) {
    Map<String, Tag> playerData = new HashMap<String, Tag>();
    CompoundTag playerTag = null;
    // Map<PlayerData, Object> ret = new HashMap<PlayerData, Object>();

    File playerDir = new File(world.getName(), "players");
    if (!playerDir.exists()) playerDir.mkdirs();

    File playerFile = new File(playerDir, player.getName() + ".dat");
    if (!playerFile.exists()) {
      try {
        playerFile.createNewFile();
      } catch (IOException e) {
        e.printStackTrace();
      }
    } else {
      try {
        NBTInputStream in = new NBTInputStream(new FileInputStream(playerFile));
        playerTag = (CompoundTag) in.readTag();
        in.close();
        if (playerTag != null) playerData.putAll(playerTag.getValue());
      } catch (EOFException e) {
      } catch (IOException e) {
        player.kickPlayer("Failed to read " + player.getName() + ".dat!");
        server
            .getLogger()
            .severe(
                "Failed to read player.dat for player "
                    + player.getName()
                    + " in world "
                    + world.getName()
                    + "!");
        e.printStackTrace();
      }
    }

    if (playerTag == null) playerTag = new CompoundTag("", new HashMap<String, Tag>());
    EntityStoreLookupService.find(GlowPlayer.class).load(player, playerTag);
  }
  public void writePlayerData(GlowPlayer player) {

    File playerDir = new File(world.getName(), "players");
    if (!playerDir.exists()) playerDir.mkdirs();

    File playerFile = new File(playerDir, player.getName() + ".dat");
    if (!playerFile.exists())
      try {
        playerFile.createNewFile();
      } catch (IOException e) {
        player.getSession().disconnect("Failed to access player.dat");
        server
            .getLogger()
            .severe(
                "Failed to access player.dat for player "
                    + player.getName()
                    + " in world "
                    + world.getName()
                    + "!");
      }

    Map<String, Tag> out = EntityStoreLookupService.find(GlowPlayer.class).save(player);
    try {
      NBTOutputStream outStream = new NBTOutputStream(new FileOutputStream(playerFile));
      outStream.writeTag(new CompoundTag("", out));
      outStream.close();
    } catch (IOException e) {
      player.getSession().disconnect("Failed to write player.dat", true);
      server
          .getLogger()
          .severe(
              "Failed to write player.dat for player "
                  + player.getName()
                  + " in world "
                  + world.getName()
                  + "!");
    }
  }
  public WorldFinalValues readWorldData() throws IOException {
    Map<String, Tag> level = new HashMap<String, Tag>();

    File levelFile = new File(dir, "level.dat");
    if (!levelFile.exists()) {
      try {
        levelFile.createNewFile();
      } catch (IOException e) {
        handleWorldException("level.dat", e);
      }
    } else {
      try {
        NBTInputStream in = new NBTInputStream(new FileInputStream(levelFile));
        CompoundTag levelTag = (CompoundTag) in.readTag();
        in.close();
        if (levelTag != null) level.putAll(levelTag.getValue());
      } catch (EOFException e) {
      } catch (IOException e) {
        handleWorldException("level.dat", e);
      }
    }
    UUID uid = null;
    File uuidFile = new File(dir, "uid.dat");
    if (!uuidFile.exists()) {
      try {
        uuidFile.createNewFile();
      } catch (IOException e) {
        handleWorldException("uid.dat", e);
      }
    } else {
      DataInputStream str = null;
      try {
        str = new DataInputStream(new FileInputStream(uuidFile));
        uid = new UUID(str.readLong(), str.readLong());
      } catch (EOFException e) {
      } finally {
        if (str != null) {
          str.close();
        }
      }
    }
    long seed = 0L;
    if (level.containsKey("thundering")) {
      ByteTag thunderTag = (ByteTag) level.remove("thundering");
      world.setThundering(thunderTag.getValue() == 1);
    }
    if (level.containsKey("raining")) {
      ByteTag rainTag = (ByteTag) level.remove("raining");
      world.setStorm(rainTag.getValue() == 1);
    }
    if (level.containsKey("thunderTime")) {
      IntTag thunderTimeTag = (IntTag) level.remove("thunderTime");
      world.setThunderDuration(thunderTimeTag.getValue());
    }
    if (level.containsKey("rainTime")) {
      IntTag rainTimeTag = (IntTag) level.remove("rainTime");
      world.setWeatherDuration(rainTimeTag.getValue());
    }
    if (level.containsKey("RandomSeed")) {
      LongTag seedTag = (LongTag) level.remove("RandomSeed");
      seed = seedTag.getValue();
    }
    if (level.containsKey("Time")) {
      LongTag timeTag = (LongTag) level.remove("Time");
      world.setTime(timeTag.getValue());
    }
    if (level.containsKey("SpawnX") && level.containsKey("SpawnY") && level.containsKey("SpawnZ")) {
      IntTag spawnXTag = (IntTag) level.remove("SpawnX");
      IntTag spawnYTag = (IntTag) level.remove("SpawnY");
      IntTag spawnZTag = (IntTag) level.remove("SpawnZ");
      world.setSpawnLocation(spawnXTag.getValue(), spawnYTag.getValue(), spawnZTag.getValue());
    }
    unknownTags.putAll(level);
    if (uid == null) uid = UUID.randomUUID();
    return new WorldFinalValues(seed, uid);
  }
 private void handleWorldException(String file, IOException e) {
   server.unloadWorld(world, false);
   server.getLogger().severe("Unable to access " + file + " for world " + world.getName());
   e.printStackTrace();
 }
  @Override
  public void handle(GlowSession session, DiggingMessage message) {
    // Todo: Implement SHOOT_ARROW_FINISH_EATING
    // Todo: Implement SWAP_ITEM_IN_HAND
    GlowPlayer player = session.getPlayer();
    GlowWorld world = player.getWorld();
    GlowBlock block = world.getBlockAt(message.getX(), message.getY(), message.getZ());
    BlockFace face = BlockPlacementHandler.convertFace(message.getFace());
    ItemStack holding = player.getItemInHand();

    if (block.getRelative(face).getType() == Material.FIRE) {
      block.getRelative(face).breakNaturally();
      return; // returns to avoid breaking block in creative
    }

    boolean blockBroken = false;
    boolean revert = false;
    if (message.getState() == DiggingMessage.START_DIGGING || player.getDigging() == null) {
      // call interact event
      Action action = Action.LEFT_CLICK_BLOCK;
      Block eventBlock = block;
      if (player.getLocation().distanceSquared(block.getLocation()) > 36
          || block.getTypeId() == 0) {
        action = Action.LEFT_CLICK_AIR;
        eventBlock = null;
      }
      PlayerInteractEvent interactEvent =
          EventFactory.onPlayerInteract(player, action, eventBlock, face);

      // blocks don't get interacted with on left click, so ignore that
      // attempt to use item in hand, that is, dig up the block
      if (!BlockPlacementHandler.selectResult(interactEvent.useItemInHand(), true)) {
        // the event was cancelled, get out of here
        revert = true;
      } else if (player.getGameMode() != GameMode.SPECTATOR) {
        player.setDigging(null);
        // emit damage event - cancel by default if holding a sword
        boolean instaBreak =
            player.getGameMode() == GameMode.CREATIVE
                || block.getMaterialValues().getHardness() == 0;
        BlockDamageEvent damageEvent =
            new BlockDamageEvent(player, block, player.getItemInHand(), instaBreak);
        if (player.getGameMode() == GameMode.CREATIVE
            && holding != null
            && EnchantmentTarget.WEAPON.includes(holding.getType())) {
          damageEvent.setCancelled(true);
        }
        EventFactory.callEvent(damageEvent);

        // follow orders
        if (damageEvent.isCancelled()) {
          revert = true;
        } else {
          // in creative, break even if denied in the event, or the block
          // can never be broken (client does not send DONE_DIGGING).
          blockBroken = damageEvent.getInstaBreak();
          if (!blockBroken) {
            /// TODO: add a delay here based on hardness
            player.setDigging(block);
          }
        }
      }
    } else if (message.getState() == DiggingMessage.FINISH_DIGGING) {
      // shouldn't happen in creative mode

      // todo: verification against malicious clients
      blockBroken = block.equals(player.getDigging());

      if (blockBroken
          && holding.getType() != Material.AIR
          && holding.getDurability() != holding.getType().getMaxDurability()) {
        switch (block.getType()) {
          case GRASS:
          case DIRT:
          case SAND:
          case GRAVEL:
          case MYCEL:
          case SOUL_SAND:
            switch (holding.getType()) {
              case WOOD_SPADE:
              case STONE_SPADE:
              case IRON_SPADE:
              case GOLD_SPADE:
              case DIAMOND_SPADE:
                holding.setDurability((short) (holding.getDurability() + 1));
                break;
              default:
                holding.setDurability((short) (holding.getDurability() + 2));
                break;
            }
            break;
          case LOG:
          case LOG_2:
          case WOOD:
          case CHEST:
            switch (holding.getType()) {
              case WOOD_AXE:
              case STONE_AXE:
              case IRON_AXE:
              case GOLD_AXE:
              case DIAMOND_AXE:
                holding.setDurability((short) (holding.getDurability() + 1));
                break;
              default:
                holding.setDurability((short) (holding.getDurability() + 2));
                break;
            }
            break;
          case STONE:
          case COBBLESTONE:
            break;
          default:
            holding.setDurability((short) (holding.getDurability() + 2));
            break;
        }
        if (holding.getDurability() >= holding.getType().getMaxDurability()) {
          player.getInventory().remove(holding);
          // player.getItemInHand().setType(Material.AIR);
        }
      }
      player.setDigging(null);
    } else if (message.getState() == DiggingMessage.STATE_DROP_ITEM) {
      player.dropItemInHand(false);
      return;
    } else if (message.getState() == DiggingMessage.STATE_DROP_ITEMSTACK) {
      player.dropItemInHand(true);
      return;
    } else if (message.getState() == DiggingMessage.STATE_SHOT_ARROW_FINISH_EATING
        && player.getUsageItem() != null) {
      if (player.getUsageItem().equals(holding)) {
        ItemType type = ItemTable.instance().getItem(player.getUsageItem().getType());
        ((ItemTimedUsage) type).endUse(player, player.getUsageItem());
      } else {
        // todo: verification against malicious clients
        // todo: inform player their item is wrong
      }
      return;
    } else {
      return;
    }

    if (blockBroken && !revert) {
      // fire the block break event
      BlockBreakEvent breakEvent = EventFactory.callEvent(new BlockBreakEvent(block, player));
      if (breakEvent.isCancelled()) {
        BlockPlacementHandler.revert(player, block);
        return;
      }

      BlockType blockType = ItemTable.instance().getBlock(block.getType());
      if (blockType != null) {
        blockType.blockDestroy(player, block, face);
      }

      // destroy the block
      if (!block.isEmpty()
          && !block.isLiquid()
          && player.getGameMode() != GameMode.CREATIVE
          && world.getGameRuleMap().getBoolean("doTileDrops")) {
        for (ItemStack drop : block.getDrops(holding)) {
          GlowItem item = world.dropItemNaturally(block.getLocation(), drop);
          item.setPickupDelay(30);
          item.setBias(player);
        }
      }

      player.addExhaustion(0.025f);

      // STEP_SOUND actually is the block break particles
      world.playEffectExceptTo(
          block.getLocation(), Effect.STEP_SOUND, block.getTypeId(), 64, player);
      GlowBlockState state = block.getState();
      block.setType(Material.AIR);
      if (blockType != null) {
        blockType.afterDestroy(player, block, face, state);
      }
    } else if (revert) {
      // replace the block that wasn't really dug
      BlockPlacementHandler.revert(player, block);
    } else if (block.getType() != Material.AIR) {
      BlockType blockType = ItemTable.instance().getBlock(block.getType());
      blockType.leftClickBlock(player, block, holding);
    }
  }