/*
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.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.bukkit.Chunk;
import org.bukkit.DyeColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.World;
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.Blaze;
import org.bukkit.entity.Creature;
import org.bukkit.entity.Creeper;
import org.bukkit.entity.EnderDragon;
import org.bukkit.entity.Enderman;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.Fireball;
import org.bukkit.entity.Ghast;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Item;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.MagmaCube;
import org.bukkit.entity.Monster;
import org.bukkit.entity.PigZombie;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.Sheep;
import org.bukkit.entity.Skeleton;
import org.bukkit.entity.Skeleton.SkeletonType;
import org.bukkit.entity.Spider;
import org.bukkit.entity.ThrownPotion;
import org.bukkit.entity.Witch;
import org.bukkit.entity.Wither;
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.EntityChangeBlockEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.entity.EntityTeleportEvent;
import org.bukkit.event.entity.ItemSpawnEvent;
import org.bukkit.event.entity.PotionSplashEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.event.entity.SheepRegrowWoolEvent;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
//handles events related to entities
class EntityEventHandler implements Listener
{
//when there's an explosion...
@EventHandler(priority = EventPriority.NORMAL)
public void onExplosion(EntityExplodeEvent event)
{
World world = event.getLocation().getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
Entity entity = event.getEntity();
//FEATURE: bigger TNT booms, all explosions have 100% block yield
if(ExtraHardMode.instance.config_betterTNT)
{
event.setYield(1);
if(entity != null && entity.getType() == EntityType.PRIMED_TNT && !ExtraHardMode.instance.config_workAroundExplosionsBugs)
{
//create more explosions nearby
long serverTime = world.getFullTime();
int random1 = (int)(serverTime + entity.getLocation().getBlockZ()) % 8;
int random2 = (int)(serverTime + entity.getLocation().getBlockX()) % 8;
Location [] locations = new Location [4];
locations[0] = entity.getLocation().add(random1, 1, random2);
locations[1] = entity.getLocation().add(-random2, 0, random1 / 2);
locations[2] = entity.getLocation().add(-random1 / 2, -1, -random2);
locations[3] = entity.getLocation().add(random1 / 2, 0, -random2 / 2);
for(int i = 0; i < locations.length; i++)
{
CreateExplosionTask task = new CreateExplosionTask(locations[i], 6F);
ExtraHardMode.instance.getServer().getScheduler().scheduleSyncDelayedTask(ExtraHardMode.instance, task, 3L * (i + 1));
}
}
}
//FEATURE: ender dragon fireballs may summon minions and/or set fires
if(entity != null && entity.getType() == EntityType.FIREBALL)
{
Fireball fireball = (Fireball)entity;
Entity spawnedMonster = null;
if(fireball.getShooter() != null && fireball.getShooter().getType() == EntityType.ENDER_DRAGON)
{
int random = ExtraHardMode.randomNumberGenerator.nextInt(100);
if(random < 40)
{
spawnedMonster = entity.getWorld().spawnEntity(entity.getLocation(), EntityType.BLAZE);
for(int x1 = -2; x1 <= 2; x1++)
{
for(int z1 = -2; z1 <= 2; z1++)
{
for(int y1 = 2; y1 >= -2; y1--)
{
Block block = fireball.getLocation().add(x1, y1, z1).getBlock();
Material underType = block.getRelative(BlockFace.DOWN).getType();
if(block.getType() == Material.AIR && underType != Material.AIR && underType != Material.FIRE)
{
block.setType(Material.FIRE);
}
}
}
}
Location location = fireball.getLocation().add(0, 1, 0);
for(int i = 0; i < 10; i++)
{
FallingBlock fire = world.spawnFallingBlock(location, Material.FIRE, (byte)0);
Vector velocity = Vector.getRandom();
if(velocity.getY() < 0)
{
velocity.setY(velocity.getY() * -1);
}
if(ExtraHardMode.randomNumberGenerator.nextBoolean())
{
velocity.setZ(velocity.getZ() * -1);
}
if(ExtraHardMode.randomNumberGenerator.nextBoolean())
{
velocity.setX(velocity.getX() * -1);
}
fire.setVelocity(velocity);
}
}
else if(random < 70)
{
for(int i = 0; i < 2; i++)
{
spawnedMonster = (Zombie)entity.getWorld().spawnEntity(entity.getLocation(), EntityType.ZOMBIE);
EntityEventHandler.markLootLess((LivingEntity)spawnedMonster);
Zombie zombie = (Zombie)spawnedMonster;
zombie.setVillager(true);
}
}
else
{
spawnedMonster = entity.getWorld().spawnEntity(entity.getLocation(), EntityType.ENDERMAN);
}
}
if(spawnedMonster != null)
{
EntityEventHandler.markLootLess((LivingEntity)spawnedMonster);
}
}
//FEATURE: in hardened stone mode, TNT only softens stone to cobble
if(ExtraHardMode.instance.config_superHardStone)
{
List<Block> blocks = event.blockList();
for(int i = 0; i < blocks.size(); i++)
{
Block block = blocks.get(i);
if(block.getType() == Material.STONE)
{
block.setType(Material.COBBLESTONE);
blocks.remove(i--);
}
//FEATURE: more falling blocks
ExtraHardMode.physicsCheck(block, 0, true);
}
}
//FEATURE: more powerful ghast fireballs
if(entity != null && entity instanceof Fireball && !ExtraHardMode.instance.config_workAroundExplosionsBugs)
{
Fireball fireball = (Fireball)entity;
if(fireball.getShooter() != null && fireball.getShooter().getType() == EntityType.GHAST)
{
event.setCancelled(true);
entity.getWorld().createExplosion(entity.getLocation(), 4F, true); //same as vanilla TNT, plus fire
}
}
//FEATURE: bigger creeper explosions (for more-frequent cave-ins)
if(entity != null && entity instanceof Creeper && !ExtraHardMode.instance.config_workAroundExplosionsBugs)
{
event.setCancelled(true);
entity.getWorld().createExplosion(entity.getLocation(), 3F, false); //same as vanilla TNT
}
}
//when a splash potion breaks...
@EventHandler(priority = EventPriority.LOW)
public void onPotionSplash(PotionSplashEvent event)
{
ThrownPotion potion = event.getPotion();
Location location = potion.getLocation();
World world = location.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
//FEATURE: enhanced witches. they throw wolf spawner and teleport potions as well as poison potions
LivingEntity shooter = potion.getShooter();
if(shooter.getType() == EntityType.WITCH)
{
Witch witch = (Witch)shooter;
int random = ExtraHardMode.randomNumberGenerator.nextInt(100);
boolean makeExplosion = false;
//30% summon zombie
if(random < 30)
{
event.setCancelled(true);
Entity [] entities = location.getChunk().getEntities();
boolean zombieNearby = false;
for(int j = 0; j < entities.length; j++)
{
if(entities[j].getType() == EntityType.ZOMBIE)
{
Zombie zombie = (Zombie)entities[j];
if(zombie.isVillager() && zombie.isBaby())
{
zombieNearby = true;
break;
}
}
}
if(!zombieNearby)
{
Zombie zombie = (Zombie)location.getWorld().spawnEntity(location, EntityType.ZOMBIE);
zombie.setVillager(true);
zombie.setBaby(true);
if(zombie.getTarget() != null)
{
zombie.setTarget(witch.getTarget());
}
markLootLess(zombie);
}
else
{
makeExplosion = true;
}
}
//30% teleport
else if (random < 60)
{
event.setCancelled(true);
witch.teleport(location);
}
//30% explosion
else if(random < 90)
{
event.setCancelled(true);
makeExplosion = true;
}
//otherwise poison potion (selective target)
else
{
Collection<LivingEntity> targets = event.getAffectedEntities();
Iterator<LivingEntity> iterator = targets.iterator();
while(iterator.hasNext())
{
LivingEntity target = iterator.next();
if(target.getType() != EntityType.PLAYER)
{
event.setIntensity(target, 0);
}
}
}
//if explosive potion, direct damage to players in the area
if(makeExplosion)
{
//explosion just for show, no damage
location.getWorld().createExplosion(location, 0F);
Collection<LivingEntity> targets = event.getAffectedEntities();
Iterator<LivingEntity> iterator = targets.iterator();
while(iterator.hasNext())
{
LivingEntity target = iterator.next();
if(target.getType() == EntityType.PLAYER)
{
target.damage(3);
}
}
}
}
}
//when a creature spawns...
@EventHandler(priority = EventPriority.LOW)
public void onEntitySpawn(CreatureSpawnEvent event)
{
Location location = event.getLocation();
World world = location.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
//avoid infinite loops
if(event.getSpawnReason() == SpawnReason.CUSTOM) return;
LivingEntity entity = event.getEntity();
EntityType entityType = entity.getType();
//FEATURE: inhibited monster grinders/farms
if(ExtraHardMode.instance.config_inhibitMonsterGrinders)
{
SpawnReason reason = event.getSpawnReason();
//spawners and spawn eggs always spawn a monster, but the monster doesn't drop any loot
if(reason == SpawnReason.SPAWNER && (ExtraHardMode.instance.config_bonusNetherBlazeSpawnPercent > 0 || !(entity instanceof Blaze)))
{
EntityEventHandler.markLootLess(entity);
}
//otherwise, consider environment to stop monsters from spawning in non-natural places
else if((reason == SpawnReason.NATURAL || reason == SpawnReason.VILLAGE_INVASION) && entity instanceof Monster)
{
Environment environment = location.getWorld().getEnvironment();
Material underBlockType = location.getBlock().getRelative(BlockFace.DOWN).getType();
if(environment == Environment.NORMAL)
{
if( underBlockType != Material.GRASS &&
underBlockType != Material.STONE &&
underBlockType != Material.SAND &&
underBlockType != Material.GRAVEL &&
underBlockType != Material.MOSSY_COBBLESTONE &&
underBlockType != Material.OBSIDIAN &&
underBlockType != Material.COBBLESTONE &&
underBlockType != Material.BEDROCK &&
underBlockType != Material.AIR && //bats
underBlockType != Material.WATER) //squid
{
event.setCancelled(true);
return;
}
}
else if(environment == Environment.NETHER)
{
if( underBlockType != Material.NETHERRACK &&
underBlockType != Material.NETHER_BRICK &&
underBlockType != Material.SOUL_SAND &&
underBlockType != Material.AIR ) //ghasts
{
event.setCancelled(true);
return;
}
}
else
{
if( underBlockType != Material.ENDER_STONE &&
underBlockType != Material.OBSIDIAN &&
underBlockType != Material.AIR ) //ender dragon
{
event.setCancelled(true);
return;
}
}
}
}
//FEATURE: charged creeper spawns
if(entityType == EntityType.CREEPER)
{
if(ExtraHardMode.random(ExtraHardMode.instance.config_chargedCreeperSpawnPercent))
{
((Creeper)entity).setPowered(true);
}
}
//FEATURE: more witches above ground (on grass)
if(entityType == EntityType.ZOMBIE && world.getEnvironment() == Environment.NORMAL && entity.getLocation().getBlock().getRelative(BlockFace.DOWN).getType() == Material.GRASS)
{
if(ExtraHardMode.random(ExtraHardMode.instance.config_bonusWitchSpawnPercent))
{
event.setCancelled(true);
entityType = EntityType.WITCH;
world.spawnEntity(location, entityType);
}
}
//FEATURE: more spiders underground
if(entityType == EntityType.ZOMBIE && world.getEnvironment() == Environment.NORMAL && location.getBlockY() < world.getSeaLevel() - 5)
{
if(ExtraHardMode.random(ExtraHardMode.instance.config_bonusUndergroundSpiderSpawnPercent))
{
event.setCancelled(true);
entityType = EntityType.SPIDER;
world.spawnEntity(location, entityType);
}
}
//FEATURE: blazes near bedrock
else if(entityType == EntityType.SKELETON && world.getEnvironment() == Environment.NORMAL && location.getBlockY() < 20)
{
if(ExtraHardMode.random(ExtraHardMode.instance.config_nearBedrockBlazeSpawnPercent))
{
event.setCancelled(true);
entityType = EntityType.BLAZE;
world.spawnEntity(location, entityType);
}
}
//FEATURE: more blazes
else if(entityType == EntityType.PIG_ZOMBIE)
{
if(ExtraHardMode.random(ExtraHardMode.instance.config_bonusNetherBlazeSpawnPercent))
{
event.setCancelled(true);
entityType = EntityType.BLAZE;
//FEATURE: magma cubes spawn with blazes
if(ExtraHardMode.random(ExtraHardMode.instance.config_flameSlimesSpawnWithNetherBlazePercent))
{
MagmaCube cube = (MagmaCube)(world.spawnEntity(location, EntityType.MAGMA_CUBE));
cube.setSize(1);
}
world.spawnEntity(location, entityType);
}
}
//FEATURE: extra monster spawns underground
if(ExtraHardMode.instance.config_moreMonstersMaxY > 0)
{
if( world.getEnvironment() == Environment.NORMAL &&
event.getLocation().getBlockY() < ExtraHardMode.instance.config_moreMonstersMaxY &&
entity instanceof Monster &&
entityType != null)
{
for(int i = 1; i < ExtraHardMode.instance.config_moreMonstersMultiplier; i++)
{
Entity newEntity = world.spawnEntity(event.getLocation(), entityType);
if(EntityEventHandler.isLootLess(entity))
{
EntityEventHandler.markLootLess((LivingEntity)newEntity);
}
}
}
}
//FEATURE: always-angry pig zombies
if(ExtraHardMode.instance.config_alwaysAngryPigZombies)
{
if(entity instanceof PigZombie)
{
PigZombie pigZombie = (PigZombie)entity;
pigZombie.setAnger(Integer.MAX_VALUE);
}
}
}
//when an entity shoots a bow...
@EventHandler
public void onShootProjectile(ProjectileLaunchEvent event)
{
Location location = event.getEntity().getLocation();
World world = location.getWorld();
EntityType entityType = event.getEntityType();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
if(event.getEntity() == null) return;
//FEATURE: skeletons sometimes release silverfish to attack their targets
if(entityType != EntityType.ARROW) return;
Arrow arrow = (Arrow)event.getEntity();
LivingEntity shooter = arrow.getShooter();
if(shooter != null && shooter.getType() == EntityType.SKELETON && ExtraHardMode.random(ExtraHardMode.instance.config_skeletonsReleaseSilverfishPercent))
{
Skeleton skeleton = (Skeleton)shooter;
//cancel arrow fire
event.setCancelled(true);
//replace with silverfish, quarter velocity of arrow, wants to attack same target as skeleton
Creature silverFish = (Creature)world.spawnEntity(skeleton.getLocation().add(0, 1.5, 0), EntityType.SILVERFISH);
silverFish.setVelocity(arrow.getVelocity().multiply(.25));
silverFish.setTarget(skeleton.getTarget());
EntityEventHandler.markLootLess(silverFish); //this silverfish doesn't drop loot
}
}
//when a chunk loads...
@EventHandler
public void onChunkLoad(ChunkLoadEvent event)
{
Chunk chunk = event.getChunk();
World world = chunk.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
//FEATURE: always-angry pig zombies
if(ExtraHardMode.instance.config_alwaysAngryPigZombies)
{
Entity [] entities = chunk.getEntities();
for(int i = 0; i < entities.length; i++)
{
if(entities[i] instanceof PigZombie)
{
PigZombie pigZombie = (PigZombie)entities[i];
pigZombie.setAnger(Integer.MAX_VALUE);
}
}
}
}
//when an entity dies...
@EventHandler
public void onEntityDeath(EntityDeathEvent event)
{
LivingEntity entity = event.getEntity();
World world = entity.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
//FEATURE: some portion of player inventory is permanently lost on death
if(entity instanceof Player)
{
Player player = (Player)entity;
if(!player.hasPermission("extrahardmode.bypass"))
{
List<ItemStack> drops = event.getDrops();
int numberOfStacksToRemove = (int)(drops.size() * (ExtraHardMode.instance.config_playerDeathItemStacksForfeitPercent / 100f));
for(int i = 0; i < numberOfStacksToRemove && drops.size() > 0; i++)
{
int indexOfStackToRemove = ExtraHardMode.randomNumberGenerator.nextInt(drops.size());
drops.remove(indexOfStackToRemove);
}
}
}
//FEATURE: silverfish drop cobblestone
if(entity.getType() == EntityType.SILVERFISH)
{
event.getDrops().add(new ItemStack(Material.COBBLESTONE));
}
//FEATURE: zombies may reanimate if not on fire when they die
if(ExtraHardMode.instance.config_zombiesReanimatePercent > 0)
{
if(entity.getType() == EntityType.ZOMBIE)
{
Zombie zombie = (Zombie)entity;
if(!zombie.isVillager() && entity.getFireTicks() < 1 && ExtraHardMode.random(ExtraHardMode.instance.config_zombiesReanimatePercent))
{
Player playerTarget = null;
Entity target = zombie.getTarget();
if(target instanceof Player)
{
playerTarget = (Player)target;
}
RespawnZombieTask task = new RespawnZombieTask(entity.getLocation(), playerTarget);
int respawnSeconds = ExtraHardMode.randomNumberGenerator.nextInt(6) + 3; //3-8 seconds
ExtraHardMode.instance.getServer().getScheduler().scheduleSyncDelayedTask(ExtraHardMode.instance, task, 20L * respawnSeconds); ///20L ~ 1 second
}
}
}
//FEATURE: creepers may drop activated TNT when they die
if(ExtraHardMode.instance.config_creepersDropTNTOnDeathPercent > 0)
{
if(entity.getType() == EntityType.CREEPER && ExtraHardMode.random(ExtraHardMode.instance.config_creepersDropTNTOnDeathPercent))
{
world.spawnEntity(entity.getLocation(), EntityType.PRIMED_TNT);
}
}
//FEATURE: pig zombies drop nether wart when slain in nether fortresses
if(ExtraHardMode.instance.config_fortressPigsDropWart && world.getEnvironment() == Environment.NETHER && entity instanceof PigZombie)
{
Block underBlock = entity.getLocation().getBlock().getRelative(BlockFace.DOWN);
if(underBlock.getType() == Material.NETHER_BRICK)
{
event.getDrops().add(new ItemStack(Material.NETHER_STALK));
}
}
//FEATURE: nether blazes drop extra loot (glowstone and gunpowder)
if(ExtraHardMode.instance.config_blazesDropBonusLoot && entity instanceof Blaze)
{
if(world.getEnvironment() == Environment.NETHER)
{
//50% chance of each
if(ExtraHardMode.randomNumberGenerator.nextInt(2) == 0)
{
event.getDrops().add(new ItemStack(Material.SULPHUR, 2));
}
else
{
event.getDrops().add(new ItemStack(Material.GLOWSTONE_DUST, 2));
}
}
else //no drops in the normal world (restricting blaze rods to the nether)
{
event.getDrops().clear();
}
}
//FEATURE: ender dragon drops prizes on death
if(entity instanceof EnderDragon)
{
if(ExtraHardMode.instance.config_enderDragonDropsVillagerEggs)
{
ItemStack itemStack = new ItemStack(Material.MONSTER_EGG, 2, (short)120);
world.dropItemNaturally(entity.getLocation().add(10, 0, 0), itemStack);
}
if(ExtraHardMode.instance.config_enderDragonDropsEgg)
{
world.dropItemNaturally(entity.getLocation().add(10, 0, 0), new ItemStack(Material.DRAGON_EGG));
}
if(ExtraHardMode.instance.config_enderDragonCombatAnnouncements)
{
StringBuilder builder = new StringBuilder("The dragon has been defeated! ( By: ");
for(Player player : this.playersFightingDragon)
{
builder.append(player.getName() + " ");
}
builder.append(")");
ExtraHardMode.instance.getServer().broadcastMessage(builder.toString());
}
if(ExtraHardMode.instance.config_enderDragonNoBuilding)
{
for(Player player : this.playersFightingDragon)
{
ExtraHardMode.sendMessage(player, TextMode.Success, Messages.DragonFountainTip);
}
}
this.playersFightingDragon.clear();
}
//FEATURE: monsters which take environmental damage or spawn from spawners don't drop loot and exp (monster grinder inhibitor)
if(ExtraHardMode.instance.config_inhibitMonsterGrinders && entity.getType() != EntityType.PLAYER && entity.getType() != EntityType.SQUID)
{
boolean noLoot = false;
if(EntityEventHandler.isLootLess(entity))
{
noLoot = true;
}
else if(entity instanceof Skeleton)
{
Skeleton skeleton = (Skeleton)entity;
if(skeleton.getSkeletonType() == SkeletonType.WITHER && skeleton.getEyeLocation().getBlock().getType() != Material.AIR)
{
noLoot = true;
}
}
else if(entity instanceof Enderman)
{
if(entity.getEyeLocation().getBlock().getType() != Material.AIR)
{
noLoot = true;
}
}
else
{
//also no loot for monsters which die standing in water
Block block = entity.getLocation().getBlock();
Block underBlock = block.getRelative(BlockFace.DOWN);
Block [] adjacentBlocks = new Block []
{
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.NORTH_WEST),
block.getRelative(BlockFace.SOUTH_WEST),
underBlock,
underBlock.getRelative(BlockFace.EAST),
underBlock.getRelative(BlockFace.WEST),
underBlock.getRelative(BlockFace.NORTH),
underBlock.getRelative(BlockFace.SOUTH),
underBlock.getRelative(BlockFace.NORTH_EAST),
underBlock.getRelative(BlockFace.SOUTH_EAST),
underBlock.getRelative(BlockFace.NORTH_WEST),
underBlock.getRelative(BlockFace.SOUTH_WEST)
};
for(int i = 0; i < adjacentBlocks.length; i++)
{
block = adjacentBlocks[i];
if(block.getType() == Material.WATER || block.getType() == Material.STATIONARY_WATER)
{
noLoot = true;
break;
}
}
//also no loot for monsters who can't reach their (melee) killers
Player killer = entity.getKiller();
if(killer != null)
{
Location monsterEyeLocation = entity.getEyeLocation();
Location playerEyeLocation = killer.getEyeLocation();
//interpolate locations
Location [] locations = new Location []
{
new Location(
monsterEyeLocation.getWorld(),
.2 * monsterEyeLocation.getX() + .8 * playerEyeLocation.getX(),
monsterEyeLocation.getY(),
.2 * monsterEyeLocation.getZ() + .8 * playerEyeLocation.getZ()),
new Location(
monsterEyeLocation.getWorld(),
.5 * monsterEyeLocation.getX() + .5 * playerEyeLocation.getX(),
monsterEyeLocation.getY(),
.5 * monsterEyeLocation.getZ() + .5 * playerEyeLocation.getZ()),
new Location(
monsterEyeLocation.getWorld(),
.8 * monsterEyeLocation.getX() + .2 * playerEyeLocation.getX(),
monsterEyeLocation.getY(),
.8 * monsterEyeLocation.getZ() + .2 * playerEyeLocation.getZ()),
};
for(int i = 0; i < locations.length; i++)
{
Location middleLocation = locations[i];
//monster is blocked at eye level, unable to advance toward killer
if(middleLocation.getBlock().getType() != Material.AIR)
{
noLoot = true;
}
//monster doesn't have room above to hurdle a foot level block, unable to advance toward killer
else
{
Block bottom = middleLocation.getBlock().getRelative(BlockFace.DOWN);
Block top = middleLocation.getBlock().getRelative(BlockFace.UP);
if(top.getType() != Material.AIR && bottom.getType() != Material.AIR ||
bottom.getType() == Material.FENCE ||
bottom.getType() == Material.FENCE_GATE ||
bottom.getType() == Material.COBBLE_WALL ||
bottom.getType() == Material.NETHER_FENCE)
{
noLoot = true;
}
}
}
}
}
if(noLoot)
{
event.setDroppedExp(0);
event.getDrops().clear();
}
}
//FEATURE: animals don't drop experience (because they're easy to "farm")
if(ExtraHardMode.instance.config_inhibitMonsterGrinders && entity instanceof Animals)
{
event.setDroppedExp(0);
}
//FEATURE: ghasts deflect arrows and drop extra loot and exp
if(ExtraHardMode.instance.config_ghastsDeflectArrows)
{
if(entity instanceof Ghast)
{
event.setDroppedExp(event.getDroppedExp() * 10);
List<ItemStack> itemDrops = event.getDrops();
for(int i = 0; i < itemDrops.size(); i++)
{
ItemStack itemDrop = itemDrops.get(i);
itemDrop.setAmount(itemDrop.getAmount() * 10);
}
}
}
//FEATURE: blazes explode on death in normal world
if(ExtraHardMode.instance.config_blazesExplodeOnDeath && entity instanceof Blaze && world.getEnvironment() == Environment.NORMAL && !ExtraHardMode.instance.config_workAroundExplosionsBugs)
{
//create explosion
world.createExplosion(entity.getLocation(), 2F, true); //equal to a TNT blast, sets fires
//fire a fireball straight up in normal worlds
Fireball fireball = (Fireball) world.spawnEntity(entity.getLocation(), EntityType.FIREBALL);
fireball.setDirection(new Vector(0, 10, 0));
fireball.setYield(1);
}
//FEATURE: nether blazes may multiply on death
if(ExtraHardMode.instance.config_netherBlazesSplitOnDeathPercent > 0 && world.getEnvironment() == Environment.NETHER && entity instanceof Blaze)
{
if(ExtraHardMode.random(ExtraHardMode.instance.config_netherBlazesSplitOnDeathPercent))
{
Entity firstNewBlaze = world.spawnEntity(entity.getLocation(), EntityType.BLAZE);
firstNewBlaze.setVelocity(new Vector(1, 0, 1));
Entity secondNewBlaze = world.spawnEntity(entity.getLocation(), EntityType.BLAZE);
secondNewBlaze.setVelocity(new Vector(-1, 0, -1));
//if this blaze was marked lootless, mark the new blazes the same
if(EntityEventHandler.isLootLess((LivingEntity)entity))
{
EntityEventHandler.markLootLess((LivingEntity)firstNewBlaze);
EntityEventHandler.markLootLess((LivingEntity)secondNewBlaze);
}
}
}
//FEATURE: spiders drop web on death
if(ExtraHardMode.instance.config_spidersDropWebOnDeath)
{
if(entity instanceof Spider)
{
//random web placement
long serverTime = world.getFullTime();
int random1 = (int)(serverTime + entity.getLocation().getBlockZ()) % 9;
int random2 = (int)(serverTime + entity.getLocation().getBlockX()) % 9;
Location [] locations = new Location [4];
locations[0] = entity.getLocation().add(random1, 0, random2);
locations[1] = entity.getLocation().add(-random2, 0, random1 / 2);
locations[2] = entity.getLocation().add(-random1 / 2, 0, -random2);
locations[3] = entity.getLocation().add(random1 / 2, 0, -random2 / 2);
ArrayList<Block> changedBlocks = new ArrayList<Block>();
for(int i = 0; i < locations.length; i++)
{
Location location = locations[i];
Block block = location.getBlock();
//don't replace anything solid with web
if(block.getType() != Material.AIR) continue;
//only place web on the ground, not hanging up in the air
do
{
block = block.getRelative(BlockFace.DOWN);
}while(block.getType() == Material.AIR);
//don't place web over fluids or stack webs
if(!block.isLiquid() && block.getType() != Material.WEB)
{
block = block.getRelative(BlockFace.UP);
//don't place next to cactus, because it will break the cactus
Block [] adjacentBlocks = new Block []
{
block.getRelative(BlockFace.EAST),
block.getRelative(BlockFace.WEST),
block.getRelative(BlockFace.NORTH),
block.getRelative(BlockFace.SOUTH)
};
boolean nextToCactus = false;
for(int j = 0; j < adjacentBlocks.length; j++)
{
if(adjacentBlocks[j].getType() == Material.CACTUS)
{
nextToCactus = true;
break;
}
}
if(!nextToCactus)
{
block.setType(Material.WEB);
changedBlocks.add(block);
}
}
}
//any webs placed above sea level will be automatically cleaned up after a short time
if(entity.getLocation().getBlockY() >= entity.getLocation().getWorld().getSeaLevel() - 5)
{
WebCleanupTask task = new WebCleanupTask(changedBlocks);
ExtraHardMode.instance.getServer().getScheduler().scheduleSyncDelayedTask(ExtraHardMode.instance, task, 20L * 30);
}
}
}
}
private ArrayList<Player> playersFightingDragon = new ArrayList<Player>();
//when an entity is damaged
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onEntityDamage (EntityDamageEvent event)
{
Entity entity = event.getEntity();
EntityType entityType = entity.getType();
World world = entity.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
//is this an entity damaged by entity event?
EntityDamageByEntityEvent subEvent = null;
if(event instanceof EntityDamageByEntityEvent)
{
subEvent = (EntityDamageByEntityEvent)event;
}
//FEATURE: don't allow explosions to destroy items on the ground
//REASONS: charged creepers explode twice, enhanced TNT explodes 5 times
if(entityType == EntityType.DROPPED_ITEM)
{
event.setCancelled(true);
}
//FEATURE: the dragon has new attacks
if(subEvent != null && entity.getType() == EntityType.ENDER_DRAGON && ExtraHardMode.instance.config_enderDragonAdditionalAttacks)
{
Player damager = null;
if(subEvent.getDamager() instanceof Player)
{
damager = (Player) subEvent.getDamager();
}
else if(subEvent.getDamager() instanceof Projectile)
{
Projectile projectile = (Projectile)subEvent.getDamager();
if(projectile.getShooter() != null && projectile.getShooter() instanceof Player)
{
damager = (Player)projectile.getShooter();
}
}
if(damager != null)
{
if(!this.playersFightingDragon.contains(damager))
{
this.playersFightingDragon.add(damager);
DragonAttackPatternTask task = new DragonAttackPatternTask((LivingEntity)entity, damager, this.playersFightingDragon);
ExtraHardMode.instance.getServer().getScheduler().scheduleSyncDelayedTask(ExtraHardMode.instance, task, 1L);
if(ExtraHardMode.instance.config_enderDragonCombatAnnouncements)
{
ExtraHardMode.instance.getServer().broadcastMessage(damager.getName() + " is challenging the dragon!");
}
}
for(int i = 0; i < 5; i++)
{
DragonAttackTask task = new DragonAttackTask(entity, damager);
ExtraHardMode.instance.getServer().getScheduler().scheduleSyncDelayedTask(ExtraHardMode.instance, task, 20L * (ExtraHardMode.randomNumberGenerator.nextInt(15)));
}
Chunk chunk = damager.getLocation().getChunk();
Entity [] entities = chunk.getEntities();
for(int i = 0; i < entities.length; i++)
{
if(entities[i].getType() == EntityType.ENDERMAN)
{
Enderman enderman = (Enderman)entities[i];
enderman.setTarget(damager);
}
}
}
}
//FEATURE: zombies can apply a debilitating effect
if(ExtraHardMode.instance.config_zombiesDebilitatePlayers)
{
if(subEvent != null && subEvent.getDamager() instanceof Zombie)
{
if(entity instanceof Player)
{
Player player = (Player)entity;
if(!player.hasPermission("extrahardmode.bypass"))
{
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 20 * 10, 3));
}
}
}
}
//FEATURE: magma cubes become blazes when they take damage
if(entityType == EntityType.MAGMA_CUBE && ExtraHardMode.instance.config_magmaCubesBecomeBlazesOnDamage && !entity.isDead() && !ExtraHardMode.instance.config_workAroundExplosionsBugs)
{
entity.remove(); //remove magma cube
entity.getWorld().spawnEntity(entity.getLocation().add(0, 2, 0), EntityType.BLAZE); //replace with blaze
entity.getWorld().createExplosion(entity.getLocation(), 2F, true); //fiery explosion for effect
}
//FEATURE: arrows pass through skeletons
if(entityType == EntityType.SKELETON && subEvent != null && ExtraHardMode.instance.config_skeletonsDeflectArrowsPercent > 0)
{
Entity damageSource = subEvent.getDamager();
//only arrows
if(damageSource instanceof Arrow)
{
Arrow arrow = (Arrow)damageSource;
//percent chance
if(ExtraHardMode.random(ExtraHardMode.instance.config_skeletonsDeflectArrowsPercent))
{
//cancel the damage
event.setCancelled(true);
//teleport the arrow a single block farther along its flight path
//note that .6 and 12 were the unexplained recommended values for speed and spread, reflectively, in the bukkit wiki
arrow.remove();
world.spawnArrow(arrow.getLocation().add((arrow.getVelocity().normalize()).multiply(2)), arrow.getVelocity(), .6f, 12f);
}
}
}
//FEATURE: extra damage and effects from environmental damage
if(ExtraHardMode.instance.config_enhancedEnvironmentalDamage)
{
Player player = null;
if(entity instanceof Player)
{
player = (Player)entity;
}
if(player != null && !player.hasPermission("extrahardmode.bypass"))
{
DamageCause cause = event.getCause();
if(event.getDamage() > 2 && (cause == DamageCause.BLOCK_EXPLOSION || cause == DamageCause.ENTITY_EXPLOSION))
{
player.addPotionEffect(new PotionEffect(PotionEffectType.CONFUSION, 20 * 15, 3));
}
else if(cause == DamageCause.FALL)
{
player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW, 20 * event.getDamage(), 4));
event.setDamage(event.getDamage() * 2);
}
else if(cause == DamageCause.SUFFOCATION)
{
event.setDamage(event.getDamage() * 5);
}
else if(cause == DamageCause.LAVA)
{
event.setDamage(event.getDamage() * 2);
}
else if(cause == DamageCause.FIRE_TICK)
{
player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 20 * 1, 1));
}
}
}
//FEATURE: skeletons can knock back
if(ExtraHardMode.instance.config_skeletonsKnockBackPercent > 0)
{
if(subEvent != null)
{
if(subEvent.getDamager() instanceof Arrow)
{
Arrow arrow = (Arrow)(subEvent.getDamager());
if(arrow.getShooter() != null && arrow.getShooter() instanceof Skeleton)
{
if(ExtraHardMode.random(ExtraHardMode.instance.config_skeletonsKnockBackPercent))
{
//cut damage in half
event.setDamage(event.getDamage() / 2);
//knock back target with half the arrow's velocity
entity.setVelocity(arrow.getVelocity());
}
}
}
}
}
//FEATURE: monsters trapped in webbing break out of the webbing when hit
if(entity instanceof Monster)
{
this.clearWebbing(entity);
}
//FEATURE: blazes drop fire on hit
if(ExtraHardMode.instance.config_blazesDropFireOnDamage)
{
if(entityType == EntityType.BLAZE)
{
Blaze blaze = (Blaze)entity;
if(blaze.getHealth() > blaze.getMaxHealth() / 2)
{
Block block = entity.getLocation().getBlock();
Block underBlock = block.getRelative(BlockFace.DOWN);
while(underBlock.getType() == Material.AIR)
underBlock = underBlock.getRelative(BlockFace.DOWN);
block = underBlock.getRelative(BlockFace.UP);
if(block.getType() == Material.AIR && underBlock.getType() != Material.AIR && !underBlock.isLiquid() && underBlock.getY() > 0)
{
block.setType(Material.FIRE);
}
}
}
}
//FEATURE: charged creepers explode on hit
if(ExtraHardMode.instance.config_chargedCreepersExplodeOnHit && !ExtraHardMode.instance.config_workAroundExplosionsBugs)
{
if(entityType == EntityType.CREEPER && !entity.isDead())
{
Creeper creeper = (Creeper)entity;
if(creeper.isPowered())
{
markLootLess((LivingEntity)entity);
entity.remove();
world.createExplosion(entity.getLocation(), 4F); //equal to a TNT blast
}
}
}
//FEATURE: flaming creepers explode
if(ExtraHardMode.instance.config_flamingCreepersExplode && !ExtraHardMode.instance.config_workAroundExplosionsBugs)
{
if(entityType == EntityType.CREEPER && !entity.isDead())
{
Creeper creeper = (Creeper)entity;
if(creeper.getFireTicks() > 0 && ExtraHardMode.randomNumberGenerator.nextBoolean())
{
markLootLess((LivingEntity)entity);
entity.remove();
world.createExplosion(entity.getLocation(), 4F); //equal to a TNT blast
}
}
}
//FEATURE: ghasts deflect arrows and drop extra loot
if(ExtraHardMode.instance.config_ghastsDeflectArrows)
{
//only ghasts, and only if damaged by another entity (as opposed to environmental damage)
if(entity instanceof Ghast && event instanceof EntityDamageByEntityEvent)
{
Entity damageSource = subEvent.getDamager();
//only arrows
if(damageSource instanceof Arrow)
{
//who shot it?
Arrow arrow = (Arrow)damageSource;
if(arrow.getShooter() != null && arrow.getShooter() instanceof Player)
{
//check permissions when it's shot by a player
Player player = (Player)arrow.getShooter();
event.setCancelled(!player.hasPermission("extrahardmode.bypass"));
}
else
{
//otherwise always deflect
event.setCancelled(true);
return;
}
}
}
}
//FEATURE: monsters which take environmental damage don't drop loot or experience (monster grinder inhibitor)
if(ExtraHardMode.instance.config_inhibitMonsterGrinders && entity instanceof LivingEntity)
{
DamageCause damageCause = event.getCause();
if(damageCause != DamageCause.ENTITY_ATTACK && damageCause != DamageCause.PROJECTILE && damageCause != DamageCause.BLOCK_EXPLOSION)
{
EntityEventHandler.addEnvironmentalDamage((LivingEntity)entity, event.getDamage());
}
}
}
//when a sheep regrows its wool...
@EventHandler
public void onSheepRegrowWool(SheepRegrowWoolEvent event)
{
World world = event.getEntity().getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
//FEATURE: sheep are all white, and may be dyed only temporarily
if(ExtraHardMode.instance.config_sheepRegrowWhiteWool)
{
Sheep sheep = event.getEntity();
sheep.setColor(DyeColor.WHITE);
}
}
//when an entity (not a player) teleports...
@EventHandler
public void onEntityTeleport(EntityTeleportEvent event)
{
Entity entity = event.getEntity();
World world = entity.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
if(world.getEnvironment() != Environment.NORMAL) return;
if(entity instanceof Enderman && ExtraHardMode.instance.config_improvedEndermanTeleportation)
{
Enderman enderman = (Enderman)entity;
//ignore endermen which aren't fighting players
if(enderman.getTarget() == null || !(enderman.getTarget() instanceof Player)) return;
//ignore endermen which are taking damage from the environment (to avoid rapid teleportation due to rain or suffocation)
if(enderman.getLastDamageCause() != null && enderman.getLastDamageCause().getCause() != DamageCause.ENTITY_ATTACK) return;
//ignore endermen which are in caves (standing on stone)
if(enderman.getLocation().getBlock().getRelative(BlockFace.DOWN).getType() == Material.STONE) return;
Player player = (Player)enderman.getTarget();
//ignore when player is in a different world from the enderman
if(!player.getWorld().equals(enderman.getWorld())) return;
//half the time, teleport the player instead
if(ExtraHardMode.random(50))
{
event.setCancelled(true);
int distanceSquared = (int)player.getLocation().distanceSquared(enderman.getLocation());
//play sound at old location
world.playSound(player.getLocation(), Sound.ENDERMAN_TELEPORT, 1, 1);
Block destinationBlock;
//if the player is far away
if(distanceSquared > 75)
{
//have the enderman swap places with the player
destinationBlock = enderman.getLocation().getBlock();
enderman.teleport(player.getLocation());
}
//otherwise if the player is close
else
{
//teleport the player to the enderman's destination
destinationBlock = event.getTo().getBlock();
}
while(destinationBlock.getType() != Material.AIR || destinationBlock.getRelative(BlockFace.UP).getType() != Material.AIR)
{
destinationBlock = destinationBlock.getRelative(BlockFace.UP);
}
player.teleport(destinationBlock.getLocation(), TeleportCause.ENDER_PEARL);
//play sound at new location
world.playSound(player.getLocation(), Sound.ENDERMAN_TELEPORT, 1, 1);
}
}
}
//when an entity targets something (as in to attack it)...
@EventHandler
public void onEntityTarget(EntityTargetEvent event)
{
Entity entity = event.getEntity();
World world = entity.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
//FEATURE: a monster which gains a target breaks out of any webbing it might have been trapped within
if(entity instanceof Monster)
{
this.clearWebbing(entity);
}
//FEATURE: monsters don't target the ender dragon
if(event.getTarget() != null && event.getTarget() instanceof EnderDragon)
{
event.setCancelled(true);
}
}
//when a player crafts something...
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onItemCrafted(CraftItemEvent event)
{
HumanEntity entity = event.getWhoClicked();
if(entity == null || !(entity instanceof Player)) return;
Player player = (Player)entity;
World world = player.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world) || player.hasPermission("extrahardmode.bypass")) return;
Material result = event.getRecipe().getResult().getType();
//FEATURE: no crafting melon seeds
if(ExtraHardMode.instance.config_weakFoodCrops && result == Material.MELON_SEEDS || result == Material.PUMPKIN_SEEDS)
{
event.setCancelled(true);
ExtraHardMode.sendMessage(player, TextMode.Instr, Messages.NoCraftingMelonSeeds);
return;
}
//FEATURE: extra TNT from the TNT recipe
if(ExtraHardMode.instance.config_betterTNT && event.getRecipe().getResult().getType() == Material.TNT)
{
player.getInventory().addItem(new ItemStack(Material.TNT, 2));
}
}
//when a player teleports BUG HERE: last i checked, this event didn't fire from bukkit. so this code is incomplete (i stopped working on it)
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onPlayerTeleport(PlayerTeleportEvent event)
{
Player player = event.getPlayer();
World world = player.getWorld();
if(event.getCause() != TeleportCause.END_PORTAL || !ExtraHardMode.instance.config_enabled_worlds.contains(world) || player.hasPermission("extrahardmode.bypass") || world.getEnvironment() == Environment.THE_END) return;
}
//when an item spawns
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onItemSpawn(ItemSpawnEvent event)
{
//FEATURE: fountain effect from dragon fireball explosions sometimes causes fire to drop as an item. this is the fix for that.
Item item = event.getEntity();
World world = item.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world) || world.getEnvironment() != Environment.THE_END) return;
if(item.getItemStack().getType() == Material.FIRE)
{
event.setCancelled(true);
}
}
//when an entity tries to change a block (does not include player block changes)
//don't allow endermen to change blocks
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOW)
public void onEntityChangeBlock(EntityChangeBlockEvent event)
{
Block block = event.getBlock();
World world = block.getWorld();
if(!ExtraHardMode.instance.config_enabled_worlds.contains(world)) return;
if(event.getEntity().getType() == EntityType.SILVERFISH && event.getTo() == Material.MONSTER_EGGS)
{
event.setCancelled(true);
}
}
//marks an entity so that the plugin can remember not to drop loot or experience if it's killed
static void markLootLess(LivingEntity entity)
{
entity.setMetadata("extrahard_environmentalDamage", new FixedMetadataValue(ExtraHardMode.instance, entity.getMaxHealth()));
}
//tracks total environmental damage done to an entity
static void addEnvironmentalDamage(LivingEntity entity, int damage)
{
if(!entity.hasMetadata("extrahard_environmentalDamage"))
{
entity.setMetadata("extrahard_environmentalDamage", new FixedMetadataValue(ExtraHardMode.instance, damage));
}
else
{
int currentTotalDamage = entity.getMetadata("extrahard_environmentalDamage").get(0).asInt();
entity.setMetadata("extrahard_environmentalDamage", new FixedMetadataValue(ExtraHardMode.instance, currentTotalDamage + damage));
}
}
//checks whether an entity should drop items when it dies
static boolean isLootLess(LivingEntity entity)
{
if(entity instanceof Creature && entity.hasMetadata("extrahard_environmentalDamage"))
{
int totalDamage = entity.getMetadata("extrahard_environmentalDamage").get(0).asInt();
if(!(entity instanceof Wither))
{
return (totalDamage > entity.getMaxHealth() / 2);
}
else
{
return false; //wither is exempt. he can't be farmed because creating him requires combining not-farmable components
}
}
return false;
}
//clears any webbing which may be trapping this entity (assumes two-block-tall entity)
void clearWebbing(Entity entity)
{
Block feetBlock = entity.getLocation().getBlock();
Block headBlock = feetBlock.getRelative(BlockFace.UP);
Block [] blocks = {feetBlock, headBlock};
for(int i = 0; i < blocks.length; i++)
{
Block block = blocks[i];
if(block.getType() == Material.WEB)
{
block.setType(Material.AIR);
}
}
}
}