/* ExtraHardMode Server Plugin for Minecraft Copyright (C) 2012 Ryan Hamshire This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package me.ryanhamshire.ExtraHardMode; import java.util.List; import org.bukkit.Chunk; import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.block.Biome; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockDispenseEvent; import org.bukkit.event.block.BlockGrowEvent; import org.bukkit.event.block.BlockPistonExtendEvent; import org.bukkit.event.block.BlockPistonRetractEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.weather.WeatherChangeEvent; import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.material.Torch; import org.bukkit.util.Vector; //event handlers related to blocks public class BlockEventHandler implements Listener { //constructor public BlockEventHandler() { } //when a player breaks a block... @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onBlockBreak(BlockBreakEvent breakEvent) { Block block = breakEvent.getBlock(); World world = block.getWorld(); Player player = breakEvent.getPlayer(); if(!ExtraHardMode.instance.config_enabled_worlds.contains(world) || player.hasPermission("extrahardmode.bypass")) return; //FEATURE: very limited building in the end //players are allowed to break only end stone, and only to create a stair up to ground level if(ExtraHardMode.instance.config_enderDragonNoBuilding && world.getEnvironment() == Environment.THE_END) { if(block.getType() != Material.ENDER_STONE) { breakEvent.setCancelled(true); ExtraHardMode.sendMessage(player, TextMode.Err, Messages.LimitedEndBuilding); return; } else { int absoluteDistanceFromBlock = Math.abs(block.getX() - player.getLocation().getBlockX()); int zdistance = Math.abs(block.getZ() - player.getLocation().getBlockZ()); if(zdistance > absoluteDistanceFromBlock) { absoluteDistanceFromBlock = zdistance; } if(block.getY() < player.getLocation().getBlockY() + absoluteDistanceFromBlock) { breakEvent.setCancelled(true); ExtraHardMode.sendMessage(player, TextMode.Err, Messages.LimitedEndBuilding); return; } } } //FEATURE: stone breaks tools much more quickly if(ExtraHardMode.instance.config_superHardStone) { ItemStack inHandStack = player.getItemInHand(); //if breaking stone with an item in hand and the player does NOT have the bypass permission if( block.getType() == Material.STONE && inHandStack != null) { //if not using an iron or diamond pickaxe, don't allow breakage and explain to the player Material tool = inHandStack.getType(); if(tool != Material.IRON_PICKAXE && tool != Material.DIAMOND_PICKAXE) { ExtraHardMode.sendMessage(player, TextMode.Instr, Messages.StoneMiningHelp); breakEvent.setCancelled(true); return; } //otherwise, drastically reduce tool durability when breaking stone else { short amount = 0; if(tool == Material.IRON_PICKAXE) amount = 8; else amount = 22; inHandStack.setDurability((short)(inHandStack.getDurability() + amount)); } } //when ore is broken, it softens adjacent stone //important to ensure players can reach the ore they break if(block.getType().name().endsWith("_ORE")) { Block adjacentBlock = block.getRelative(BlockFace.DOWN); if(adjacentBlock.getType() == Material.STONE) adjacentBlock.setType(Material.COBBLESTONE); adjacentBlock = block.getRelative(BlockFace.UP); if(adjacentBlock.getType() == Material.STONE) adjacentBlock.setType(Material.COBBLESTONE); adjacentBlock = block.getRelative(BlockFace.EAST); if(adjacentBlock.getType() == Material.STONE) adjacentBlock.setType(Material.COBBLESTONE); adjacentBlock = block.getRelative(BlockFace.WEST); if(adjacentBlock.getType() == Material.STONE) adjacentBlock.setType(Material.COBBLESTONE); adjacentBlock = block.getRelative(BlockFace.NORTH); if(adjacentBlock.getType() == Material.STONE) adjacentBlock.setType(Material.COBBLESTONE); adjacentBlock = block.getRelative(BlockFace.SOUTH); if(adjacentBlock.getType() == Material.STONE) adjacentBlock.setType(Material.COBBLESTONE); } } //FEATURE: trees chop more naturally if(block.getType() == Material.LOG && ExtraHardMode.instance.config_betterTreeChopping) { Block rootBlock = block; while(rootBlock.getType() == Material.LOG) { rootBlock = rootBlock.getRelative(BlockFace.DOWN); } if(rootBlock.getType() == Material.DIRT || rootBlock.getType() == Material.GRASS) { Block aboveLog = block.getRelative(BlockFace.UP); while(aboveLog.getType() == Material.LOG) { ExtraHardMode.applyPhysics(aboveLog); aboveLog = aboveLog.getRelative(BlockFace.UP); } } } //FEATURE: more falling blocks ExtraHardMode.physicsCheck(block, 0, true); //FEATURE: no nether wart farming (always drops exactly 1 nether wart when broken) if(ExtraHardMode.instance.config_noFarmingNetherWart) { if(block.getType() == Material.NETHER_WARTS) { block.getDrops().clear(); block.getDrops().add(new ItemStack(Material.NETHER_STALK)); } } //FEATURE: breaking netherrack may start a fire if(ExtraHardMode.instance.config_brokenNetherrackCatchesFirePercent > 0 && block.getType() == Material.NETHERRACK) { Block underBlock = block.getRelative(BlockFace.DOWN); if(underBlock.getType() == Material.NETHERRACK && ExtraHardMode.random(ExtraHardMode.instance.config_brokenNetherrackCatchesFirePercent)) { breakEvent.setCancelled(true); block.setType(Material.FIRE); } } } //when a player places a block... @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onBlockPlace(BlockPlaceEvent placeEvent) { Player player = placeEvent.getPlayer(); Block block = placeEvent.getBlock(); World world = block.getWorld(); if(!ExtraHardMode.instance.config_enabled_worlds.contains(world) || player.hasPermission("extrahardmode.bypass") || player.getGameMode() == GameMode.CREATIVE) return; //FEATURE: very limited building in the end //players are allowed to break only end stone, and only to create a stair up to ground level if(ExtraHardMode.instance.config_enderDragonNoBuilding && world.getEnvironment() == Environment.THE_END) { placeEvent.setCancelled(true); ExtraHardMode.sendMessage(player, TextMode.Err, Messages.LimitedEndBuilding); return; } //FIX: prevent players from placing ore as an exploit to work around the hardened stone rule if(ExtraHardMode.instance.config_superHardStone && block.getType().name().endsWith("_ORE")) { Block [] adjacentBlocks = new Block [] { block.getRelative(BlockFace.DOWN), block.getRelative(BlockFace.UP), block.getRelative(BlockFace.EAST), block.getRelative(BlockFace.WEST), block.getRelative(BlockFace.NORTH), block.getRelative(BlockFace.SOUTH) }; for(int i = 0; i < adjacentBlocks.length; i++) { Block adjacentBlock = adjacentBlocks[i]; if(adjacentBlock.getType() == Material.STONE) { ExtraHardMode.sendMessage(player, TextMode.Err, Messages.NoPlacingOreAgainstStone); placeEvent.setCancelled(true); return; } } } //FEATURE: no farming nether wart if(block.getType() == Material.NETHER_WARTS && ExtraHardMode.instance.config_noFarmingNetherWart) { placeEvent.setCancelled(true); return; } //FEATURE: more falling blocks ExtraHardMode.physicsCheck(block, 0, true); //FEATURE: no standard torches, jack o lanterns, or fire on top of netherrack near diamond level if(ExtraHardMode.instance.config_standardTorchMinY > 0) { if( world.getEnvironment() == Environment.NORMAL && block.getY() < ExtraHardMode.instance.config_standardTorchMinY && (block.getType() == Material.TORCH || block.getType() == Material.JACK_O_LANTERN || (block.getType() == Material.FIRE && block.getRelative(BlockFace.DOWN).getType() == Material.NETHERRACK))) { ExtraHardMode.sendMessage(player, TextMode.Instr, Messages.NoTorchesHere); placeEvent.setCancelled(true); return; } } //FEATURE: players can't place blocks from weird angles (using shift to hover over in the air beyond the edge of solid ground) //or directly beneath themselves, for that matter if(ExtraHardMode.instance.config_limitedBlockPlacement) { if( block.getX() == player.getLocation().getBlockX() && block.getZ() == player.getLocation().getBlockZ() && block.getY() < player.getLocation().getBlockY() ) { ExtraHardMode.sendMessage(player, TextMode.Instr, Messages.RealisticBuilding); placeEvent.setCancelled(true); return; } Block underBlock = player.getLocation().getBlock().getRelative(BlockFace.DOWN); //if standing directly over lava, prevent placement if(underBlock.getType() == Material.LAVA || underBlock.getType() == Material.STATIONARY_LAVA) { ExtraHardMode.sendMessage(player, TextMode.Instr, Messages.RealisticBuilding); placeEvent.setCancelled(true); return; } //otherwise if hovering over air, check one block lower else if(underBlock.getType() == Material.AIR) { underBlock = underBlock.getRelative(BlockFace.DOWN); //if over lava or more air, prevent placement if(underBlock.getType() == Material.AIR || underBlock.getType() == Material.LAVA || underBlock.getType() == Material.STATIONARY_LAVA) { ExtraHardMode.sendMessage(player, TextMode.Instr, Messages.RealisticBuilding); placeEvent.setCancelled(true); return; } } } //FEATURE: players can't attach torches to common "soft" blocks if(ExtraHardMode.instance.config_limitedTorchPlacement && block.getType() == Material.TORCH) { Torch torch = new Torch(Material.TORCH, block.getData()); Material attachmentMaterial = block.getRelative(torch.getAttachedFace()).getType(); if( attachmentMaterial == Material.DIRT || attachmentMaterial == Material.GRASS || attachmentMaterial == Material.LONG_GRASS || attachmentMaterial == Material.SAND) { placeEvent.setCancelled(true); ExtraHardMode.sendMessage(player, TextMode.Instr, Messages.LimitedTorchPlacements); return; } } } //when a dispenser dispenses... void onBlockDispense(BlockDispenseEvent event) { //FEATURE: can't move water source blocks if(ExtraHardMode.instance.config_dontMoveWaterSourceBlocks) { World world = event.getBlock().getWorld(); if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return; //only care about water if(event.getItem().getType() == Material.WATER_BUCKET) { //plan to evaporate the water next tick Block block; Vector velocity = event.getVelocity(); if(velocity.getX() > 0) { block = event.getBlock().getLocation().add(1, 0, 0).getBlock(); } else if(velocity.getX() < 0) { block = event.getBlock().getLocation().add(-1, 0, 0).getBlock(); } else if(velocity.getZ() > 0) { block = event.getBlock().getLocation().add(0, 0, 1).getBlock(); } else { block = event.getBlock().getLocation().add(0, 0, -1).getBlock(); } EvaporateWaterTask task = new EvaporateWaterTask(block); ExtraHardMode.instance.getServer().getScheduler().scheduleSyncDelayedTask(ExtraHardMode.instance, task, 1L); } } } //when a piston pushes... @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onBlockPistonExtend (BlockPistonExtendEvent event) { List<Block> blocks = event.getBlocks(); World world = event.getBlock().getWorld(); //FEATURE: prevent players from circumventing hardened stone rules by placing ore, then pushing the ore next to stone before breaking it if(!ExtraHardMode.instance.config_superHardStone || !ExtraHardMode.instance.config_enabled_worlds.contains(world)) return; //which blocks are being pushed? for(int i = 0; i < blocks.size(); i++) { //if any are ore or stone, don't push Block block = blocks.get(i); Material material = block.getType(); if(material == Material.STONE || material.name().endsWith("_ORE")) { event.setCancelled(true); return; } } } //when a piston pulls... @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onBlockPistonRetract (BlockPistonRetractEvent event) { //FEATURE: prevent players from circumventing hardened stone rules by placing ore, then pulling the ore next to stone before breaking it //we only care about sticky pistons if(!event.isSticky()) return; Block block = event.getRetractLocation().getBlock(); World world = block.getWorld(); if(!ExtraHardMode.instance.config_superHardStone || !ExtraHardMode.instance.config_enabled_worlds.contains(world)) return; Material material = block.getType(); if(material == Material.STONE || material.name().endsWith("_ORE")) { event.setCancelled(true); return; } } //when the weather changes... @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onWeatherChange (WeatherChangeEvent event) { //FEATURE: rainfall breaks exposed torches (exposed to the sky) World world = event.getWorld(); if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return; if(!event.toWeatherState()) return; //if not raining //plan to remove torches and cover crops chunk by chunk gradually throughout the rain period Chunk [] chunks = world.getLoadedChunks(); if(chunks.length > 0) { int startOffset = ExtraHardMode.randomNumberGenerator.nextInt(chunks.length); for(int i = 0; i < chunks.length; i++) { Chunk chunk = chunks[(startOffset + i) % chunks.length]; RemoveExposedTorchesTask task = new RemoveExposedTorchesTask(chunk); ExtraHardMode.instance.getServer().getScheduler().scheduleSyncDelayedTask(ExtraHardMode.instance, task, i * 20L); } } } //when a block grows... @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onBlockGrow (BlockGrowEvent event) { //FEATURE: fewer seeds = shrinking crops. when a plant grows to its full size, it may be replaced by a dead shrub if(ExtraHardMode.instance.plantDies(event.getBlock(), event.getNewState().getData().getData())) { event.setCancelled(true); event.getBlock().setType(Material.LONG_GRASS); //dead shrub } } //when a tree or mushroom grows... @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onStructureGrow(StructureGrowEvent event) { World world = event.getWorld(); Block block = event.getLocation().getBlock(); if(!ExtraHardMode.instance.config_enabled_worlds.contains(world) || (event.getPlayer() != null && event.getPlayer().hasPermission("extrahardmode.bypass"))) return; //FEATURE: no big plant growth in deserts if(ExtraHardMode.instance.config_weakFoodCrops) { Biome biome = block.getBiome(); if(biome == Biome.DESERT || biome == Biome.DESERT_HILLS) { event.setCancelled(true); } } } }