/*
   * Called when redstone changes.
   */
  @EventHandler(priority = EventPriority.HIGH)
  public void onBlockRedstoneChange(BlockRedstoneEvent event) {
    Block blockTo = event.getBlock();
    World world = blockTo.getWorld();

    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(world);

    if (wcfg.simulateSponge && wcfg.redstoneSponges) {
      int ox = blockTo.getX();
      int oy = blockTo.getY();
      int oz = blockTo.getZ();

      for (int cx = -1; cx <= 1; cx++) {
        for (int cy = -1; cy <= 1; cy++) {
          for (int cz = -1; cz <= 1; cz++) {
            Block sponge = world.getBlockAt(ox + cx, oy + cy, oz + cz);
            if (sponge.getTypeId() == 19 && sponge.isBlockIndirectlyPowered()) {
              SpongeUtil.clearSpongeWater(plugin, world, ox + cx, oy + cy, oz + cz);
            } else if (sponge.getTypeId() == 19 && !sponge.isBlockIndirectlyPowered()) {
              SpongeUtil.addSpongeWater(plugin, world, ox + cx, oy + cy, oz + cz);
            }
          }
        }
      }

      return;
    }
  }
  /*
   * Called when block physics occurs.
   */
  @EventHandler(ignoreCancelled = true)
  public void onBlockPhysics(BlockPhysicsEvent event) {
    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (cfg.activityHaltToggle) {
      event.setCancelled(true);
      return;
    }

    int id = event.getChangedTypeId();

    if (id == 13 && wcfg.noPhysicsGravel) {
      event.setCancelled(true);
      return;
    }

    if (id == 12 && wcfg.noPhysicsSand) {
      event.setCancelled(true);
      return;
    }

    if (id == 90 && wcfg.allowPortalAnywhere) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.ropeLadders && event.getBlock().getType() == Material.LADDER) {
      if (event.getBlock().getRelative(0, 1, 0).getType() == Material.LADDER) {
        event.setCancelled(true);
        return;
      }
    }
  }
  /*
   * Called when a block is destroyed from burning.
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onBlockBurn(BlockBurnEvent event) {
    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (cfg.activityHaltToggle) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.disableFireSpread) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.fireSpreadDisableToggle) {
      Block block = event.getBlock();
      event.setCancelled(true);
      checkAndDestroyAround(
          block.getWorld(), block.getX(), block.getY(), block.getZ(), BlockID.FIRE);
      return;
    }

    if (wcfg.disableFireSpreadBlocks.size() > 0) {
      Block block = event.getBlock();

      if (wcfg.disableFireSpreadBlocks.contains(block.getTypeId())) {
        event.setCancelled(true);
        checkAndDestroyAround(
            block.getWorld(), block.getX(), block.getY(), block.getZ(), BlockID.FIRE);
        return;
      }
    }

    if (wcfg.isChestProtected(event.getBlock())) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.useRegions) {
      Block block = event.getBlock();
      int x = block.getX();
      int y = block.getY();
      int z = block.getZ();
      Vector pt = toVector(block);
      RegionManager mgr = plugin.getGlobalRegionManager().get(block.getWorld());
      ApplicableRegionSet set = mgr.getApplicableRegions(pt);

      if (!set.allows(DefaultFlag.FIRE_SPREAD)) {
        checkAndDestroyAround(block.getWorld(), x, y, z, BlockID.FIRE);
        event.setCancelled(true);
        return;
      }
    }
  }
 /*
  * Called when a block yields exp
  */
 @EventHandler(priority = EventPriority.HIGH)
 public void onBlockExp(BlockExpEvent event) {
   ConfigurationManager cfg = plugin.getGlobalStateManager();
   WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());
   if (wcfg.disableExpDrops
       || !plugin
           .getGlobalRegionManager()
           .allows(DefaultFlag.EXP_DROPS, event.getBlock().getLocation())) {
     event.setExpToDrop(0);
   }
 }
  /*
   * Called when a player places a block.
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onBlockPlace(BlockPlaceEvent event) {
    Block blockPlaced = event.getBlock();
    Player player = event.getPlayer();
    World world = blockPlaced.getWorld();

    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(world);

    if (wcfg.useRegions) {
      final Location location = blockPlaced.getLocation();
      if (!plugin.getGlobalRegionManager().canBuild(player, location)
          || !plugin.getGlobalRegionManager().canConstruct(player, location)) {
        player.sendMessage(ChatColor.DARK_RED + "You don't have permission for this area.");
        event.setCancelled(true);
        return;
      }
    }

    if (wcfg.getBlacklist() != null) {
      if (!wcfg.getBlacklist()
          .check(
              new BlockPlaceBlacklistEvent(
                  plugin.wrapPlayer(player), toVector(blockPlaced), blockPlaced.getTypeId()),
              false,
              false)) {
        event.setCancelled(true);
        return;
      }
    }

    if (wcfg.signChestProtection && wcfg.getChestProtection().isChest(blockPlaced.getTypeId())) {
      if (wcfg.isAdjacentChestProtected(event.getBlock(), player)) {
        player.sendMessage(
            ChatColor.DARK_RED + "This spot is for a chest that you don't have permission for.");
        event.setCancelled(true);
        return;
      }
    }

    if (wcfg.simulateSponge && blockPlaced.getTypeId() == 19) {
      if (wcfg.redstoneSponges && blockPlaced.isBlockIndirectlyPowered()) {
        return;
      }

      int ox = blockPlaced.getX();
      int oy = blockPlaced.getY();
      int oz = blockPlaced.getZ();

      SpongeUtil.clearSpongeWater(plugin, world, ox, oy, oz);
    }
  }
  /*
   * Called when a piston retracts
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onBlockPistonRetract(BlockPistonRetractEvent event) {
    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (wcfg.useRegions && event.isSticky()) {
      if (!(plugin.getGlobalRegionManager().allows(DefaultFlag.PISTONS, event.getRetractLocation()))
          || !(plugin
              .getGlobalRegionManager()
              .allows(DefaultFlag.PISTONS, event.getBlock().getLocation()))) {
        event.setCancelled(true);
        return;
      }
    }
  }
  /*
   * Called when a block is formed by an entity.
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onEntityBlockForm(EntityBlockFormEvent event) {
    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (cfg.activityHaltToggle) {
      event.setCancelled(true);
      return;
    }

    if (event.getEntity() instanceof Snowman) {
      if (wcfg.disableSnowmanTrails) {
        event.setCancelled(true);
        return;
      }
    }
  }
  /*
   * Called when a block is damaged.
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onBlockDispense(BlockDispenseEvent event) {
    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (wcfg.blockPotions.size() > 0) {
      ItemStack item = event.getItem();
      if (item.getType() == Material.POTION && !BukkitUtil.isWaterPotion(item)) {
        Potion potion = Potion.fromDamage(BukkitUtil.getPotionEffectBits(item));
        for (PotionEffect effect : potion.getEffects()) {
          if (potion.isSplash() && wcfg.blockPotions.contains(effect.getType())) {
            event.setCancelled(true);
            return;
          }
        }
      }
    }
  }
  /*
   * Called when a piston extends
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onBlockPistonExtend(BlockPistonExtendEvent event) {
    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (wcfg.useRegions) {
      if (!plugin
          .getGlobalRegionManager()
          .allows(DefaultFlag.PISTONS, event.getBlock().getLocation())) {
        event.setCancelled(true);
        return;
      }
      for (Block block : event.getBlocks()) {
        if (!plugin.getGlobalRegionManager().allows(DefaultFlag.PISTONS, block.getLocation())) {
          event.setCancelled(true);
          return;
        }
      }
    }
  }
  /*
   * Called when a block is formed based on world conditions.
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onBlockForm(BlockFormEvent event) {
    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (cfg.activityHaltToggle) {
      event.setCancelled(true);
      return;
    }

    int type = event.getNewState().getTypeId();

    if (type == BlockID.ICE) {
      if (wcfg.disableIceFormation) {
        event.setCancelled(true);
        return;
      }
      if (wcfg.useRegions
          && !plugin
              .getGlobalRegionManager()
              .allows(DefaultFlag.ICE_FORM, event.getBlock().getLocation())) {
        event.setCancelled(true);
        return;
      }
    }

    if (type == BlockID.SNOW) {
      if (wcfg.disableSnowFormation) {
        event.setCancelled(true);
        return;
      }
      if (wcfg.useRegions
          && !plugin
              .getGlobalRegionManager()
              .allows(DefaultFlag.SNOW_FALL, event.getBlock().getLocation())) {
        event.setCancelled(true);
        return;
      }
    }
  }
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onLeavesDecay(LeavesDecayEvent event) {
    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (cfg.activityHaltToggle) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.disableLeafDecay) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.useRegions) {
      if (!plugin
          .getGlobalRegionManager()
          .allows(DefaultFlag.LEAF_DECAY, event.getBlock().getLocation())) {
        event.setCancelled(true);
      }
    }
  }
  /*
   * Called when a block fades.
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onBlockFade(BlockFadeEvent event) {

    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    int type = event.getBlock().getTypeId();

    if (type == BlockID.ICE) {
      if (wcfg.disableIceMelting) {
        event.setCancelled(true);
        return;
      }
      if (wcfg.useRegions
          && !plugin
              .getGlobalRegionManager()
              .allows(DefaultFlag.ICE_MELT, event.getBlock().getLocation())) {
        event.setCancelled(true);
        return;
      }
    }

    if (type == BlockID.SNOW) {
      if (wcfg.disableSnowMelting) {
        event.setCancelled(true);
        return;
      }
      if (wcfg.useRegions
          && !plugin
              .getGlobalRegionManager()
              .allows(DefaultFlag.SNOW_MELT, event.getBlock().getLocation())) {
        event.setCancelled(true);
        return;
      }
    }
  }
 /**
  * Get the world configuration given a world.
  *
  * @param world The world to get the configuration for.
  * @return The configuration for {@code world}
  */
 protected WorldConfiguration getWorldConfig(World world) {
   return plugin.getGlobalStateManager().get(world);
 }
  /*
   * Called when a block spreads based on world conditions.
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onBlockSpread(BlockSpreadEvent event) {
    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (cfg.activityHaltToggle) {
      event.setCancelled(true);
      return;
    }

    int fromType = event.getSource().getTypeId();

    if (fromType == BlockID.RED_MUSHROOM || fromType == BlockID.BROWN_MUSHROOM) {
      if (wcfg.disableMushroomSpread) {
        event.setCancelled(true);
        return;
      }
      if (wcfg.useRegions
          && !plugin
              .getGlobalRegionManager()
              .allows(DefaultFlag.MUSHROOMS, event.getBlock().getLocation())) {
        event.setCancelled(true);
        return;
      }
    }

    if (fromType == BlockID.GRASS) {
      if (wcfg.disableGrassGrowth) {
        event.setCancelled(true);
        return;
      }
      if (wcfg.useRegions
          && !plugin
              .getGlobalRegionManager()
              .allows(DefaultFlag.GRASS_SPREAD, event.getBlock().getLocation())) {
        event.setCancelled(true);
        return;
      }
    }

    if (fromType == BlockID.MYCELIUM) {
      if (wcfg.disableMyceliumSpread) {
        event.setCancelled(true);
        return;
      }

      if (wcfg.useRegions
          && !plugin
              .getGlobalRegionManager()
              .allows(DefaultFlag.MYCELIUM_SPREAD, event.getBlock().getLocation())) {
        event.setCancelled(true);
        return;
      }
    }

    if (fromType == BlockID.VINE) {
      if (wcfg.disableVineGrowth) {
        event.setCancelled(true);
        return;
      }

      if (wcfg.useRegions
          && !plugin
              .getGlobalRegionManager()
              .allows(DefaultFlag.VINE_GROWTH, event.getBlock().getLocation())) {
        event.setCancelled(true);
        return;
      }
    }
  }
  /*
   * Called when a block gets ignited.
   */
  @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
  public void onBlockIgnite(BlockIgniteEvent event) {
    IgniteCause cause = event.getCause();
    Block block = event.getBlock();
    World world = block.getWorld();

    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(world);

    if (cfg.activityHaltToggle) {
      event.setCancelled(true);
      return;
    }
    boolean isFireSpread = cause == IgniteCause.SPREAD;

    if (wcfg.preventLightningFire && cause == IgniteCause.LIGHTNING) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.preventLavaFire && cause == IgniteCause.LAVA) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.disableFireSpread && isFireSpread) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.blockLighter
        && (cause == IgniteCause.FLINT_AND_STEEL || cause == IgniteCause.FIREBALL)
        && event.getPlayer() != null
        && !plugin.hasPermission(event.getPlayer(), "worldguard.override.lighter")) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.fireSpreadDisableToggle && isFireSpread) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.disableFireSpreadBlocks.size() > 0 && isFireSpread) {
      int x = block.getX();
      int y = block.getY();
      int z = block.getZ();

      if (wcfg.disableFireSpreadBlocks.contains(world.getBlockTypeIdAt(x, y - 1, z))
          || wcfg.disableFireSpreadBlocks.contains(world.getBlockTypeIdAt(x + 1, y, z))
          || wcfg.disableFireSpreadBlocks.contains(world.getBlockTypeIdAt(x - 1, y, z))
          || wcfg.disableFireSpreadBlocks.contains(world.getBlockTypeIdAt(x, y, z - 1))
          || wcfg.disableFireSpreadBlocks.contains(world.getBlockTypeIdAt(x, y, z + 1))) {
        event.setCancelled(true);
        return;
      }
    }

    if (wcfg.useRegions) {
      Vector pt = toVector(block);
      Player player = event.getPlayer();
      RegionManager mgr = plugin.getGlobalRegionManager().get(world);
      ApplicableRegionSet set = mgr.getApplicableRegions(pt);

      if (player != null && !plugin.getGlobalRegionManager().hasBypass(player, world)) {
        LocalPlayer localPlayer = plugin.wrapPlayer(player);

        // this is preliminarily handled in the player listener under handleBlockRightClick
        // why it's handled here too, no one knows
        if (cause == IgniteCause.FLINT_AND_STEEL || cause == IgniteCause.FIREBALL) {
          if (!set.allows(DefaultFlag.LIGHTER)
              && !set.canBuild(localPlayer)
              && !plugin.hasPermission(player, "worldguard.override.lighter")) {
            event.setCancelled(true);
            return;
          }
        }
      }

      if (wcfg.highFreqFlags && isFireSpread && !set.allows(DefaultFlag.FIRE_SPREAD)) {
        event.setCancelled(true);
        return;
      }

      if (wcfg.highFreqFlags && cause == IgniteCause.LAVA && !set.allows(DefaultFlag.LAVA_FIRE)) {
        event.setCancelled(true);
        return;
      }

      if (cause == IgniteCause.FIREBALL && event.getPlayer() == null) {
        // wtf bukkit, FIREBALL is supposed to be reserved to players
        if (!set.allows(DefaultFlag.GHAST_FIREBALL)) {
          event.setCancelled(true);
          return;
        }
      }

      if (cause == IgniteCause.LIGHTNING && !set.allows(DefaultFlag.LIGHTNING)) {
        event.setCancelled(true);
        return;
      }
    }
  }
  /*
   * Called when fluids flow.
   */
  @EventHandler(ignoreCancelled = true)
  public void onBlockFromTo(BlockFromToEvent event) {
    World world = event.getBlock().getWorld();
    Block blockFrom = event.getBlock();
    Block blockTo = event.getToBlock();

    boolean isWater = blockFrom.getTypeId() == 8 || blockFrom.getTypeId() == 9;
    boolean isLava = blockFrom.getTypeId() == 10 || blockFrom.getTypeId() == 11;
    boolean isAir = blockFrom.getTypeId() == 0;

    ConfigurationManager cfg = plugin.getGlobalStateManager();
    WorldConfiguration wcfg = cfg.get(event.getBlock().getWorld());

    if (cfg.activityHaltToggle) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.simulateSponge && isWater) {
      int ox = blockTo.getX();
      int oy = blockTo.getY();
      int oz = blockTo.getZ();

      for (int cx = -wcfg.spongeRadius; cx <= wcfg.spongeRadius; cx++) {
        for (int cy = -wcfg.spongeRadius; cy <= wcfg.spongeRadius; cy++) {
          for (int cz = -wcfg.spongeRadius; cz <= wcfg.spongeRadius; cz++) {
            Block sponge = world.getBlockAt(ox + cx, oy + cy, oz + cz);
            if (sponge.getTypeId() == 19
                && (!wcfg.redstoneSponges || !sponge.isBlockIndirectlyPowered())) {
              event.setCancelled(true);
              return;
            }
          }
        }
      }
    }

    /*if (plugin.classicWater && isWater) {
    int blockBelow = blockFrom.getRelative(0, -1, 0).getTypeId();
    if (blockBelow != 0 && blockBelow != 8 && blockBelow != 9) {
    blockFrom.setTypeId(9);
    if (blockTo.getTypeId() == 0) {
    blockTo.setTypeId(9);
    }
    return;
    }
    }*/

    // Check the fluid block (from) whether it is air.
    // If so and the target block is protected, cancel the event
    if (wcfg.preventWaterDamage.size() > 0) {
      int targetId = blockTo.getTypeId();

      if ((isAir || isWater) && wcfg.preventWaterDamage.contains(targetId)) {
        event.setCancelled(true);
        return;
      }
    }

    if (wcfg.allowedLavaSpreadOver.size() > 0 && isLava) {
      int targetId = blockTo.getRelative(0, -1, 0).getTypeId();

      if (!wcfg.allowedLavaSpreadOver.contains(targetId)) {
        event.setCancelled(true);
        return;
      }
    }

    if (wcfg.highFreqFlags
        && isWater
        && !plugin
            .getGlobalRegionManager()
            .allows(DefaultFlag.WATER_FLOW, blockFrom.getLocation())) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.highFreqFlags
        && isLava
        && !plugin
            .getGlobalRegionManager()
            .allows(DefaultFlag.LAVA_FLOW, blockFrom.getLocation())) {
      event.setCancelled(true);
      return;
    }

    if (wcfg.disableObsidianGenerators && (isAir || isLava) && blockTo.getTypeId() == 55) {
      blockTo.setTypeId(0);
      return;
    }
  }