/*
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.List;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Horse;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.Tameable;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.FixedMetadataValue;
public class WorldEventHandler implements Listener
{
//when a chunk loads, generate a region post in that chunk if necessary
@EventHandler(ignoreCancelled = true)
public void onChunkLoad(ChunkLoadEvent chunkLoadEvent)
{
Chunk chunk = chunkLoadEvent.getChunk();
Entity [] entities = chunk.getEntities();
for(Entity entity : entities)
{
//entities which appear abandoned on chunk load get the grandfather clause treatment
if(isAbandoned(entity))
{
entity.setTicksLived(1);
}
//skeletal horses never go away unless slain. on chunk load, remove any which aren't leashed or carrying a rider
if(entity.getType() == EntityType.HORSE && PopulationDensity.instance.removeWildSkeletalHorses)
{
Horse horse = (Horse)entity;
if(horse.getVariant() == Horse.Variant.SKELETON_HORSE && !horse.isLeashed() && horse.getPassenger() == null && horse.getCustomName() == null)
{
ItemStack saddleStack = horse.getInventory().getSaddle();
if(saddleStack == null || saddleStack.getType() != Material.SADDLE)
{
horse.setHealth(0);
}
}
}
}
//nothing more to do in worlds other than the managed world
if(chunk.getWorld() != PopulationDensity.ManagedWorld) return;
//find the boundaries of the chunk
Location lesserCorner = chunk.getBlock(0, 0, 0).getLocation();
Location greaterCorner = chunk.getBlock(15, 0, 15).getLocation();
//find the center of this chunk's region
RegionCoordinates region = RegionCoordinates.fromLocation(lesserCorner);
Location regionCenter = PopulationDensity.getRegionCenter(region, false);
//if the chunk contains the region center
if( regionCenter.getBlockX() >= lesserCorner.getBlockX() && regionCenter.getBlockX() <= greaterCorner.getBlockX() &&
regionCenter.getBlockZ() >= lesserCorner.getBlockZ() && regionCenter.getBlockZ() <= greaterCorner.getBlockZ())
{
//create a task to build the post after 10 seconds
try
{
PopulationDensity.instance.dataStore.AddRegionPost(region);
}
catch(ChunkLoadException e){} //this should never happen, because the chunk is loaded (why else would onChunkLoad() be invoked?)
}
}
static boolean isAbandoned(Entity entity)
{
String customName = entity.getCustomName();
if(customName != null && !customName.isEmpty()) return false;
if((entity instanceof Tameable)) return false;
final int DAY_IN_TICKS = 1728000;
int darkMaxTicks = DAY_IN_TICKS;
int lightMaxTicks = DAY_IN_TICKS * 3;
//determine how long to wait for player interaction before deciding an entity is abandoned
if(entity instanceof Animals && PopulationDensity.instance.abandonedFarmAnimalsDie)
{
//can afford to be aggressive with animals because they're easy to re-breed
darkMaxTicks = DAY_IN_TICKS;
lightMaxTicks = DAY_IN_TICKS * 3;
}
else if(entity instanceof Minecart && PopulationDensity.instance.unusedMinecartsVanish)
{
darkMaxTicks = lightMaxTicks = DAY_IN_TICKS * 7;
}
else //if not one of the above, don't remove it even if long-lived without interaction
{
return false;
}
//triple allowance if a player nametagged the entity
if(customName != null && !customName.isEmpty())
{
darkMaxTicks *= 3;
lightMaxTicks *= 3;
}
//if in the dark, treat as wilderness creature which won't live as long
byte lightLevel = 15;
int yLocation = entity.getLocation().getBlockY();
if (yLocation < 255 && yLocation > 0)
{
lightLevel = entity.getLocation().getBlock().getLightFromBlocks();
}
if(lightLevel < 4 && entity.getTicksLived() > darkMaxTicks) //in the dark
{
return true;
}
else if(entity.getTicksLived() > lightMaxTicks) //in the light
{
//only remove if there are at least two similar entities nearby, to allow for rebreeding later
if(!(entity instanceof Minecart)) //doesn't apply to minecarts, they're more easily replaced
{
List<Entity> nearbyEntities = entity.getNearbyEntities(15, 15, 15);
int nearbySimilar = 0;
for(Entity nearby : nearbyEntities)
{
if(nearby.getType() == entity.getType() && !nearby.hasMetadata("pd_removed"))
{
nearbySimilar++;
if(nearbySimilar > 1)
{
return true;
}
}
}
}
else
{
return true;
}
}
return false;
}
@EventHandler(ignoreCancelled = true)
public void onChunkUnload(ChunkUnloadEvent event)
{
Chunk chunk = event.getChunk();
//expire any abandoned animals
removeAbandonedEntities(chunk);
//nothing more to do in worlds other than the managed world
if(chunk.getWorld() != PopulationDensity.ManagedWorld) return;
//don't allow the new player spawn point chunk to unload
//find the boundaries of the chunk
Location lesserCorner = chunk.getBlock(0, 0, 0).getLocation();
Location greaterCorner = chunk.getBlock(15, 0, 15).getLocation();
//if the region is the new player region
RegionCoordinates region = RegionCoordinates.fromLocation(lesserCorner);
if(region.equals(PopulationDensity.instance.dataStore.getOpenRegion()))
{
Location regionCenter = PopulationDensity.getRegionCenter(region, false);
//if the chunk contains the region center
if( regionCenter.getBlockX() >= lesserCorner.getBlockX() && regionCenter.getBlockX() <= greaterCorner.getBlockX() &&
regionCenter.getBlockZ() >= lesserCorner.getBlockZ() && regionCenter.getBlockZ() <= greaterCorner.getBlockZ())
{
//don't unload the chunk
event.setCancelled(true);
}
}
}
@SuppressWarnings("deprecation")
static void removeAbandonedEntities(Chunk chunk)
{
Entity [] entities = chunk.getEntities();
for(int i = 0; i < entities.length; i++)
{
Entity entity = entities[i];
if(isAbandoned(entity))
{
if(PopulationDensity.instance.markRemovedEntityLocations)
{
Block block = entity.getLocation().getBlock();
Material blockType = block.getType();
if(blockType == Material.LONG_GRASS || blockType == Material.AIR)
{
block.setTypeIdAndData(31, (byte)2, false); //fern
}
}
//eject any riders (for pigs, minecarts)
entity.eject();
//entity.remove() removes on next tick, so must mark removed entities with metadata so we know which were removed this tick
entity.setMetadata("pd_removed", new FixedMetadataValue(PopulationDensity.instance, true));
entity.remove();
}
}
}
}