/* PopulationDensity Server Plugin for Minecraft Copyright (C) 2011 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.PopulationDensity; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World.Environment; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Animals; import org.bukkit.entity.Arrow; import org.bukkit.entity.Creeper; import org.bukkit.entity.Enderman; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Item; import org.bukkit.entity.Monster; import org.bukkit.entity.Player; import org.bukkit.entity.Skeleton; import org.bukkit.entity.Spider; import org.bukkit.entity.ThrownPotion; import org.bukkit.entity.Zombie; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityToggleGlideEvent; import org.bukkit.event.entity.ItemDespawnEvent; import org.bukkit.inventory.ItemStack; public class EntityEventHandler implements Listener { //block types monsters may spawn on when grinders are disabled static HashMap<Environment, HashSet<Material>> allowedSpawnBlocks; public EntityEventHandler() { if(allowedSpawnBlocks == null) { allowedSpawnBlocks = new HashMap<Environment, HashSet<Material>>(); allowedSpawnBlocks.put(Environment.NORMAL, new HashSet<Material>(Arrays.asList( Material.GRASS, Material.SAND, Material.GRAVEL, Material.STONE, Material.MOSSY_COBBLESTONE, Material.OBSIDIAN))); allowedSpawnBlocks.put(Environment.NETHER, new HashSet<Material>(Arrays.asList( Material.NETHERRACK, Material.NETHER_BRICK))); allowedSpawnBlocks.put(Environment.THE_END, new HashSet<Material>(Arrays.asList( Material.ENDER_STONE, Material.OBSIDIAN))); } } //when an entity (includes both dynamite and creepers) explodes... @EventHandler(ignoreCancelled = true) public void onEntityExplode(EntityExplodeEvent explodeEvent) { Location location = explodeEvent.getLocation(); //if it's NOT in the managed world, let it splode (or let other plugins worry about it) RegionCoordinates region = RegionCoordinates.fromLocation(location); if(region == null) return; //otherwise if it's close to a region post Location regionCenter = PopulationDensity.getRegionCenter(region, false); regionCenter.setY(PopulationDensity.ManagedWorld.getHighestBlockYAt(regionCenter)); if(regionCenter.distanceSquared(location) < 225) //225 = 15 * 15 { explodeEvent.blockList().clear(); //All the noise and terror, none of the destruction (whew!). } //NOTE! Why not distance? Because distance squared is cheaper and will be good enough for this. } //when an item despawns //FEATURE: in the newest region only, regrow trees from fallen saplings @SuppressWarnings("deprecation") @EventHandler(ignoreCancelled = true) public void onItemDespawn (ItemDespawnEvent event) { //respect config option if(!PopulationDensity.instance.regrowTrees) return; //only care about dropped items Entity entity = event.getEntity(); if(entity.getType() != EntityType.DROPPED_ITEM) return; if(!(entity instanceof Item)) return; //get info about the dropped item ItemStack item = ((Item)entity).getItemStack(); //only care about saplings if(item.getType() != Material.SAPLING) return; //only care about the newest region if(!PopulationDensity.instance.dataStore.getOpenRegion().equals(RegionCoordinates.fromLocation(entity.getLocation()))) return; //only replace these blocks with saplings Block block = entity.getLocation().getBlock(); if(block.getType() != Material.AIR && block.getType() != Material.LONG_GRASS && block.getType() != Material.SNOW) return; //don't plant saplings next to other saplings or logs Block [] neighbors = new Block [] { block.getRelative(BlockFace.EAST), block.getRelative(BlockFace.WEST), block.getRelative(BlockFace.NORTH), block.getRelative(BlockFace.SOUTH), block.getRelative(BlockFace.NORTH_EAST), block.getRelative(BlockFace.SOUTH_EAST), block.getRelative(BlockFace.SOUTH_WEST), block.getRelative(BlockFace.NORTH_WEST) }; for(int i = 0; i < neighbors.length; i++) { if(neighbors[i].getType() == Material.SAPLING || neighbors[i].getType() == Material.LOG) return; } //only plant trees in grass or dirt Block underBlock = block.getRelative(BlockFace.DOWN); if(underBlock.getType() == Material.GRASS || underBlock.getType() == Material.DIRT) { block.setTypeIdAndData(item.getTypeId(), item.getData().getData(), false); } } @EventHandler(ignoreCancelled = true) public void onEntityDamage (EntityDamageEvent event) { Entity entity = event.getEntity(); //when an entity has fall damage immunity, it lasts for only ONE fall damage check if(event.getCause() == DamageCause.FALL) { if(PopulationDensity.instance.isFallDamageImmune(entity)) { event.setCancelled(true); PopulationDensity.instance.removeFallDamageImmunity(entity); if(entity.getType() == EntityType.PLAYER) { Player player = (Player)entity; if(!player.hasPermission("populationdensity.teleportanywhere")) { player.getWorld().createExplosion(player.getLocation(), 0); } } } } if(!(event instanceof EntityDamageByEntityEvent)) return; EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event; Player attacker = null; Entity damageSource = subEvent.getDamager(); if(damageSource instanceof Player) { attacker = (Player)damageSource; } else if(damageSource instanceof Arrow) { Arrow arrow = (Arrow)damageSource; if(arrow.getShooter() instanceof Player) { attacker = (Player)arrow.getShooter(); } } else if(damageSource instanceof ThrownPotion) { ThrownPotion potion = (ThrownPotion)damageSource; if(potion.getShooter() instanceof Player) { attacker = (Player)potion.getShooter(); } } if(attacker != null) { PopulationDensity.instance.resetIdleTimer(attacker); } } private int respawnAnimalCounter = 1; @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onEntitySpawn(CreatureSpawnEvent event) { SpawnReason reason = event.getSpawnReason(); Entity entity = event.getEntity(); //if lag has prompted PD to turn off monster grinders, limit spawns if(PopulationDensity.grindersStopped) { if(reason == SpawnReason.NETHER_PORTAL || reason == SpawnReason.SPAWNER) { event.setCancelled(true); return; } else if(reason == SpawnReason.NATURAL && entity instanceof Monster) { HashSet<Material> allowedBlockTypes = allowedSpawnBlocks.get(entity.getWorld().getEnvironment()); if(!allowedBlockTypes.contains(entity.getLocation().getBlock().getRelative(BlockFace.DOWN).getType())) { event.setCancelled(true); return; } } } //speed limit on monster grinder spawn rates - only affects grinders that rely on naturally-spawning monsters. if(reason != SpawnReason.SPAWNER_EGG && reason != SpawnReason.SPAWNER && entity instanceof Monster) { int monstersNearby = 0; List<Entity> entities = entity.getNearbyEntities(10, 20, 10); for(Entity nearbyEntity : entities) { if(nearbyEntity instanceof Monster) monstersNearby++; if(monstersNearby > PopulationDensity.instance.nearbyMonsterSpawnLimit) { event.setCancelled(true); return; } } } //natural spawns may cause animal spawns to keep new player resources available if(reason == SpawnReason.NATURAL) { if(PopulationDensity.ManagedWorld == null || event.getLocation().getWorld() != PopulationDensity.ManagedWorld) return; //when an animal naturally spawns, grow grass around it if(entity instanceof Animals && PopulationDensity.instance.regrowGrass) { this.regrow(entity.getLocation().getBlock(), 4); } //when a monster spawns, sometimes spawn animals too if(entity instanceof Monster && PopulationDensity.instance.respawnAnimals) { //only do this if the spawn is in the newest region if(!PopulationDensity.instance.dataStore.getOpenRegion().equals(RegionCoordinates.fromLocation(entity.getLocation()))) return; //if it's on grass, there's a 1/100 chance it will also spawn a group of animals Block underBlock = event.getLocation().getBlock().getRelative(BlockFace.DOWN); if(underBlock.getType() == Material.GRASS && --this.respawnAnimalCounter == 0) { this.respawnAnimalCounter = 5; //check for other nearby animals List<Entity> entities = entity.getNearbyEntities(30, 30, 30); for(int i = 0; i < entities.size(); i++) { if(entity instanceof Animals) return; } EntityType animalType = null; //decide what to spawn based on the type of monster if(entity instanceof Creeper) { animalType = EntityType.CHICKEN; } else if(entity instanceof Zombie) { animalType = EntityType.COW; } else if(entity instanceof Spider) { animalType = EntityType.SHEEP; } else if(entity instanceof Skeleton) { animalType = EntityType.PIG; } else if(entity instanceof Enderman) { if(Math.random() > 0.5) animalType = EntityType.HORSE; else animalType = EntityType.WOLF; } //spawn an animal at the entity's location and regrow some grass if(animalType != null) { entity.getWorld().spawnEntity(entity.getLocation(), animalType); this.regrow(entity.getLocation().getBlock(), 4); } } } } } @SuppressWarnings("deprecation") private void regrow(Block center, int radius) { Block toHandle; for (int x = -radius; x <= radius; x++) { for (int z = -radius; z <= radius; z++) { toHandle = center.getWorld().getBlockAt(center.getX() + x, center.getY() + 2, center.getZ() + z); while(toHandle.getType() == Material.AIR && toHandle.getY() > center.getY() - 4) toHandle = toHandle.getRelative(BlockFace.DOWN); if (toHandle.getType() == Material.GRASS) // Block is grass { Block aboveBlock = toHandle.getRelative(BlockFace.UP); if(aboveBlock.getType() == Material.AIR) { aboveBlock.setType(Material.LONG_GRASS); aboveBlock.setData((byte) 1); //data == 1 means live grass instead of dead shrub } continue; } } } } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onEntityToggleFlight(EntityToggleGlideEvent event) { if(event.getEntityType() != EntityType.PLAYER) return; if(PopulationDensity.instance.isFallDamageImmune((Player)event.getEntity())) { event.setCancelled(true); } } }